<!DOCTYPE html>
<html lang="zh-cn">
  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="author" content="Zhou Wei <zromyk@163.com>">
  <title>Linux</title>
  <link rel="shortcut icon" href="/favicon.ico">
  <link rel="stylesheet" href="/style/html/pure.css">
  <link rel="stylesheet" href="/style/html/main.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.css">
  <!-- <link rel="stylesheet" href="https://apps.bdimg.com/libs/highlight.js/9.1.0/styles/default.min.css"> -->
<link rel="stylesheet" href="/style/article/highlight/default.min.css">
<link rel="stylesheet" href="/style/article/pell-1.0.6/dist/pell.css">

</head>
<body>
  <div id="menu-background"></div>
  <div id="menu">
    <div class="pure-menu pure-menu-horizontal">
  <ul class="pure-menu-list block-middle">
    <li class="pure-menu-item">
  <a class="pure-menu-heading" href="/index.html">ZROMYK</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/index.html">主页</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/archive/index.html">归档</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/download/index.html">下载</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/feedback/index.html">反馈</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/about/index.html">关于我</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="https://github.com/zromyk"><i class="fa fa-github" style="font-size:32px"></i></a>
</li>

  </ul>
</div>

  </div>
  <div id="layout">
    <div class="content">
      <div id="nav">
    <div id="navigation" class="navigation">
  <ul class="pure-menu-list">
    <li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#io">第三章 文件 I/O</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#33-open">3.3 open</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#34-creat">3.4 creat</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#35-close">3.5 close</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#36-lseek">3.6 lseek</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#37-read">3.7 read</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#38-write">3.8 write</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#311-pread-pwrite">3.11 原子操作读写 pread 和 pwrite</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#312-dup-dup2">3.12 dup 和 dup2</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#313-syncfsyncfdatasync">3.13 sync、fsync、fdatasync</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#314-fcntl">3.14 fcntl</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#io_1">第五章 标准 I/O 库</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#53">5.3 标准输入、输出、出错</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#54">5.4 缓冲</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1">1）更改缓冲区类型</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#55">5.5 打开和关闭流</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#56">5.6 读和写流</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#57-io">5.7 每次一行 I/O</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#59-io">5.9 二进制 I/O</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#510">5.10 定位流</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_1">第六章 系统数据文件和信息</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#62">6.2 口令文件</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#63">6.3 阴影口令</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#64">6.4 组文件</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#67">6.7 其他组</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_2">第七章 进程环境</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#73">7.3 进程终止</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#exit">exit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#atexit">atexit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#75">7.5 环境表</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#76-c">7.6 C 程序运行时的内存结构</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#78">7.8 存储器分配</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1_1">1）从堆上申请内存</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#2">2）在栈上申请内存</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#3-mmp">3）内存映射 mmp</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#79">7.9 环境变量</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_3">第八章 进程控制</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#82">8.2 进程标识符</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#83-fork">8.3 fork 函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#forkexit">fork,exit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#fork_exit">fork,_exit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#vforkexit">vfork,exit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#vfork_exit">vfork,_exit</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#_4">文件共享</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#85-exit">8.5 exit 函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_5">正常终止</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_6">异常终止</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#86-wait-waitpid">8.6 wait 和 waitpid</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#810-exec">8.10 exec 族</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#811-id-id">8.11 更改用户 ID 和更改组 ID</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#813-system">8.13 system</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#814">8.14 会计进程</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_7">第十章 信号</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#103-signal">10.3 signal</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#109-kill-raise">10.9 kill 和 raise</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1010-alarm-pause">10.10 alarm 和 pause</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1011">10.11 信号集</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1012">10.12</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1013">10.13</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1014-sigaction">10.14 sigaction</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_8">第十一章 线程</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#113">11.3 线程标识</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#114">11.4 线程创建</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#115">11.5 线程终止</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_9">取消线程</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_10">线程清理处理程序</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#116">11.6 线程同步</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1161">11.6.1 互斥量</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1164">11.6.4 读写锁</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1166">11.6.6 条件变量</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1167">11.6.7 自旋锁</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1168">11.6.8 屏障</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_11">第十二章 线程控制</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#123">12.3 线程属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#124">12.4 同步属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1241">12.4.1 互斥量属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1242">12.4.2 读写锁属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1243">12.4.3 条件变量属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1244">12.4.4 屏障属性</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#125">12.5 重入</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#io_2">第十四章 高级 I/O</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#142-io">14.2 非阻塞 I/O</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#143">14.3 记录锁</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1431">14.3.1 锁的隐含继承和释放</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1432">14.3.2 在文件末端加锁</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1433">14.3.3 建议性锁和强制性锁</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#144-io">14.4 I/O多路转接</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1441-select-pselect">14.4.1 select 和 pselect</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1442-poll">14.4.2 poll</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#poll">poll 函数可用的测试值</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#interprocess-communicationipc">第十五章 进程通信（InterProcess Communication，IPC）</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#152">15.2 管道</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#153-popen-pclose">15.3 popen 和 pclose</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#154">15.4 协同进程</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#155-fifo">15.5 FIFO</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#157">15.7 消息队列</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#158">15.8 信号量</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#159">15.9 共享存储</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_12">第十六章 套接字</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#162">16.2 套接字描述符</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#163">16.3 寻址</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1631">16.3.1 字节序</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1632">16.3.2 地址格式</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#1633">16.3.3 地址查询</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#hostent">hostent</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#protoent">protoent</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#servent">servent</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#netent">netent</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#addrinfo">addrinfo</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#1634">16.3.4 将套接字与地址相关联</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#1_2">1）得到套接字相关地址</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#164">16.4 建立连接</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#connect">connect</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#listen">listen</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#accept">accept</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#send">send</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#recv">recv</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#socket">socket 端口复用</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_13">先关闭客户端再关闭服务器</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#1_3">1. 正常连接</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#2-clinet">2. 先关闭客户端 Clinet</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#3-server">3. 再关闭服务器 Server</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_14">先关闭服务器再关闭客户端</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#1_4">1. 正常连接</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#2-server">2. 先关闭服务器 Server</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#3-client">3. 再关闭客户端 Client</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav5" onclick="animateByNav()" href="#_15">可以使用端口复用技术解决</a>
</li>

  </ul>
</div>

</div>
<div id="content-articles">
  <h1 id="Linux" class="content-subhead">Linux</h1>
  <p>
    <span>2021-07-25</span>
    <span><span class="post-category post-category-other">Linux</span></span>
    <button id="button-markdownEditor" class="pure-button" onclick="markdownEditor()">启用编辑</button>
    <button id="button-save" class="pure-button" onclick="save()">保存</button>
  </p>
  <div id="content-articles-markdownEditor" style="display: none;">
    <h1>编辑 power by pell</h1>
    <div id="editor" class="pell"></div>
    <div style="margin-top:20px;">
        <h3>Text output:</h3>
        <div id="text-output"></div>
    </div>
    <div style="margin-top:20px;">
        <h3>HTML output:</h3>
        <pre id="html-output"></pre>
    </div>
  </div>
  <div id="content-articles-markdown">
    <h2 id="io">第三章 文件 I/O</h2>
<h3 id="33-open">3.3 open</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;fcntl.h&gt;

/** @brief  atexit() 用来设置一个程序正常结束前调用的函数。
 *  @param  参数 path 指向欲打开的文件路径字符串。
 *  @param  参数 flags:
 *             O_RDONLY 以只读方式打开文件
 *             O_WRONLY 以只写方式打开文件
 *             O_RDWR   以可读写方式打开文件
 *             上述三种旗标是互斥的，也就是不可同时使用，但可与下列的旗标利用OR(|)运算符组合。
 *             
 *             O_CREAT     若欲打开的文件不存在则自动建立该文件。
 *             O_EXCL      如果 O_CREAT 也被设置，此指令会去检查文件是否存在。
 *                            文件若不存在则建立该文件，否则将导致打开文件错误。
 *                            此外，若 O_CREAT 与 O_EXCL 同时设置，
 *                            并且欲打开的文件为符号连接，则会打开文件失败。
 *             O_NOCTTY    如果欲打开的文件为终端机设备时，则不会将该终端机当成进程控制终端机。
 *             O_TRUNC     若文件存在并且以可写的方式打开时，
 *                            此旗标会令文件长度清为0，而原来存于该文件的资料也会消失。
 *             O_APPEND    当读写文件时会从文件尾开始移动，
 *                            就是所写入的数据会以附加的方式加入到文件后面。
 *             O_NONBLOCK  以不可阻断的方式打开文件，
 *                            也就是无论有无数据读取或等待，都会立即返回进程之中。
 *             O_NDELAY    同 O_NONBLOCK。
 *             O_SYNC      以同步的方式打开文件。
 *             O_NOFOLLOW  如果参数pathname 所指的文件为一符号连接，则会令打开文件失败。
 *             O_DIRECTORY 如果参数pathname 所指的文件并非为一目录，则会令打开文件失败。
 *  @param  参数 mode:
 *             S_IRUSR 所有者拥有  读权限
 *             S_IWUSR 所有者拥有  写权限
 *             S_IXUSR 所有者拥有  执行权限
 *             S_IROTH 其他用户拥有 读权限
 *             S_IWOTH 其他用户拥有 写权限
 *             S_IXOTH 其他用户拥有 执行权限
 *             S_IRGRP 群组拥有    读权限
 *             S_IWGRP 群组拥有    写权限
 *             S_IXGRP 群组拥有    执行权限
 *  @return 如果执行成功则返回 0
 *             否则返回-1，失败原因存于 errno 中。
 */
int open(          const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
</code></span></code></pre>
<h3 id="34-creat">3.4 creat</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#incldue &lt;fcntl.h&gt;
int creat(const char *path, mode_t mode); // 成功则返回创建的文件的文件描述符，失败返回-1
// open(path, O_RDWR | O_CREAT | O_TRUNC, mode);
</code></span></code></pre>
<h3 id="35-close">3.5 close</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;
int close(int fd); // 成功则返回0，失败则返回-1
</code></span></code></pre>
<h3 id="36-lseek">3.6 lseek</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief  每一个已打开的文件都有一个读写位置，当打开文件时通常其读写位置是指向文件开头
 *             若是以附加的方式打开文件(如O_APPEND)，则读写位置会指向文件尾。
 *             当 read() 或 write() 时，读写位置会随之增加，lseek() 便是用来控制该文件的读写位置
 *  @param fd     打开的文件描述符 
 *  @param offset 为根据参数whence来移动读写位置的位移数
 *  @param whence 为下列其中一种:
 *             SEEK_SET 参数 offset 即为新的读写位置。
 *             SEEK_CUR 以目前的读写位置往后增加 offset 个位移量。
 *             SEEK_END 将读写位置指向文件尾后再增加 offset 个位移量。
 *             当 whence 值为 SEEK_CUR 或 SEEK_END 时，参数 offet 允许负值的出现。
 *  @return 当调用成功时则返回目前的读写位置，也就是距离文件开头多少个字节。
 *             若有错误则返回-1，errno 会存放错误代码。
 */
off_t lseek(int fd, off_t offset, int whence);
</code></span></code></pre>
<h3 id="37-read">3.7 read</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief read() 会把参数 fd 所指的文件传送 nbytes 个字节到 buf 指针所指的内存中。
 *             若参数 nbytes 为0，则 read() 不会有作用并返回0。
 *             返回值为实际读取到的字节数，如果返回0，表示已到达文件尾或是无可读取的数据，
 *             此外文件读写位置会随读取到的字节移动。
 *  @param fd     打开的文件描述符 
 *  @param buf    指向保存读取到的数据的内存
 *  @param nbytes 需要读取的字节数
 *  @return 返回值为实际读取到的字节数，如果返回0，表示已到达文件尾或是无可读取的数据，
 *             此外文件读写位置会随读取到的字节移动。
 *             若有错误则返回-1，errno 会存放错误代码，而文件读写位置则无法预期。
 *             错误代码 
 *                EINTR  此调用被信号所中断。
 *                EAGAIN 当使用不可阻断I/O 时（O_NONBLOCK），若无数据可读取则返回此值。
 *                EBADF  参数 fd 非有效的文件描述符，或该文件已关闭。
 */
ssize_t read(int fd, void *buf, size_t nbytes);
</code></span></code></pre>
<h3 id="38-write">3.8 write</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief write() 会把参数 buf 所指的内存写入 nbytes 个字节到参数fd所指的文件内。
 *  @param fd     打开的文件描述符 
 *  @param buf    指向需要写入的数据的内存
 *  @param nbytes 需要读取的字节数
 *  @return 返回值为实际读取到的字节数，如果返回0，表示已到达文件尾或是无可读取的数据，
 *             此外文件读写位置会随读取到的字节移动。
 *             若有错误则返回-1，errno 会存放错误代码，而文件读写位置则无法预期。
 *             错误代码 
 *                EINTR  此调用被信号所中断。
 *                EAGAIN 当使用不可阻断I/O 时（O_NONBLOCK），若无数据可读取则返回此值。
 *                EBADF  参数 fd 非有效的文件描述符，或该文件已关闭。
 */
ssize_t write(int fd, const void *buf, size_t nbytes);
</code></span></code></pre>
<h3 id="311-pread-pwrite">3.11 原子操作读写 pread 和 pwrite</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief 带偏移量地原子的从文件中读取数据
 *             若参数 nbytes 为0，则 read() 不会有作用并返回0。
 *             返回值为实际读取到的字节数，如果返回0，表示已到达文件尾或是无可读取的数据，
 *             此外文件读写位置会随读取到的字节移动。
 *  @param fd     打开的文件描述符 
 *  @param buf    数据缓存区指针，存放读取出来的数据
 *  @param count  读取数据的字节数
 *  @param offset 读取的起始地址的偏移量，读取地址 = 文件开始 + offset。
 *                   注意，执行后，文件偏移指针不变。
 *  @return 成功，返回成功读取数据的字节数；失败，返回-1；
 */
ssize_t pread( int fd,       void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
</code></span></code></pre>
<h3 id="312-dup-dup2">3.12 dup 和 dup2</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief 复制一个现有的文件描述符
 *  @param fd  要复制的文件描述符
 *  @param fd2 指定新文件描述符的值
 *  @return 当复制成功时，则返回最小及尚未使用的文件描述符。
 *             若有错误则返回-1，errno会存放错误代码。
 *             错误代码
 *                EBADF 参数 fd 非有效的文件描述符，或该文件已关闭。
 */
int dup(int fd);
int dup2(int fd, int fd2);
</code></span></code></pre>
<h3 id="313-syncfsyncfdatasync">3.13 sync、fsync、fdatasync</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief 负责将系统缓冲区数据写回磁盘，以确保数据同步。
 *  @param fd  指定的文件描述符
 *  @return 成功返回0，若有错误则返回-1，errno会存放错误代码。
 */
int sync(void);
int fsync(int fd);      // 同步更新文件的属性
int fdatasync(int fd);  // 只更新文件的数据部分
</code></span></code></pre>
<h3 id="314-fcntl">3.14 fcntl</h3>
<h2 id="io_1">第五章 标准 I/O 库</h2>
<h3 id="53">5.3 标准输入、输出、出错</h3>
<p>文件描述符</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#inclde &lt;unistd.h&gt;
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
</code></span></code></pre>
<p>标准 I/O 文件指针</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
stdin
stdout
stderr
</code></span></code></pre>
<h3 id="54">5.4 缓冲</h3>
<ul>
<li>全缓冲</li>
<li>缓冲区满时执行 I/O 操作。</li>
<li><strong>对于磁盘上的文件，标准 I/O 通常使用全缓冲</strong>（通过 malloc 获取缓冲区）。</li>
<li>行缓冲</li>
<li>输入输出在遇到换行符时执行 I/O 操作。</li>
<li>标准 I/O fputc 一次输出一个字符，但是只有在检测到换行符时才进行实际 I/O 操作。</li>
<li><strong>对于一个终端（如标准输入输出），通常使用行缓冲。</strong></li>
<li>从一个不带缓冲区的流或者要求从内核获取数据的流中获得数据，会冲洗缓冲区。</li>
<li>不带缓冲</li>
<li>例如，用标准 I/O fputc 写多个字符到 <strong>不带缓冲区的流</strong> 中，则它很可能对每个字符使用 write 系统调用将这些字符立即写到相关文件上。</li>
<li><strong>标准出错流 stderr 通常是不带缓冲区的。</strong>（这可以使错误信息尽快显示出来）</li>
</ul>
<blockquote class="content-quote">
<p>标准出错不带缓冲区</p>
<p>终端设备是行缓冲的</p>
<p>其他流是全缓冲的</p>
</blockquote>
<h4 id="1">1）更改缓冲区类型</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">#include &lt;stdio.h&gt;

void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
</code></span></code></pre>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>func</th>
<th>mode</th>
<th>buf</th>
<th>size</th>
<th>缓冲区类型</th>
</tr>
</thead>
<tbody>
<tr>
<td>setbuf</td>
<td></td>
<td>非空</td>
<td></td>
<td>全缓冲/行缓冲（缓冲区长度为BUFSIZ）</td>
</tr>
<tr>
<td></td>
<td></td>
<td>NULL</td>
<td></td>
<td>无缓冲</td>
</tr>
<tr>
<td>setvbuf</td>
<td>_IOFBF</td>
<td>非空</td>
<td>size</td>
<td>全缓冲</td>
</tr>
<tr>
<td></td>
<td></td>
<td>NULL</td>
<td>合适长度的系统缓冲</td>
<td></td>
</tr>
<tr>
<td></td>
<td>_IOLBF</td>
<td>非空</td>
<td>size</td>
<td>行缓冲</td>
</tr>
<tr>
<td></td>
<td></td>
<td>NULL</td>
<td>合适长度的系统缓冲</td>
<td></td>
</tr>
<tr>
<td></td>
<td>_IONBF</td>
<td></td>
<td></td>
<td>无缓冲</td>
</tr>
</tbody>
</table></div>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">#includd &lt;stdio.h&gt;
int fflush(FILE *fp); /* 冲洗流，将所有未写数据送入内核，fp为NULL时冲洗所有流 */
</code></span></code></pre>
<h3 id="55">5.5 打开和关闭流</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;

/** @brief 打开流
 *  @param path 字符串包含欲打开的文件路径及文件。
 *  @param mode 有下列几种形态字符串:
 *              r   O_RDONLY
 *              w   O_WRONLY | O_CREAT | O_TRUNC
 *              a   O_WRONLY | O_CREAT | O_APPEND
 *              r+  O_RDWR
 *              w+  O_RDWR | O_CREAT | O_TRUNC
 *              a+  O_RDWR | O_CREAT | O_APPEND
 *  @return 文件顺利打开后，指向该流的文件指针就会被返回。
 *             若果文件打开失败则返回 NULL，并把错误代码存在 errno 中。
 */
FILE *fopen(  const char *restrict path, const char *restrict type);
FILE *freopen(const char *restrict path, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
</code></span></code></pre>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
int fclose(FILE *fp); // 成功返回0，失败返回-1
</code></span></code></pre>
<h3 id="56">5.6 读和写流</h3>
<p>读</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
int getc(FILE *fp);  // 可以被实现为宏
int fgetc(FILE *fp); // 一定是一个函数，可以得到它的地址。调用时间可能比 getc 长
int getchar(void);   // getc(stdin)
// 返回值：返回读取到的字符若，已到达文件尾端或出错，返回 EOF
</code></span></code></pre>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
int ferror(FILE *fp);    // 检测出错标志
int feof(FILE *fp);      // 检测文件结束标志
// 返回值：成功返回 非0（true），失败返回 0（false）
void clearerr(FILE *fp); // 清除出错标志和文件结束标志
</code></span></code></pre>
<p>写回</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
int ungetc(int c, FILE *fp);
</code></span></code></pre>
<p>写</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
int putc(   int c, FILE *fp); // 可以被实现为宏
int fputc(  int c, FILE *fp); // 一定是一个函数，可以得到它的地址。调用时间可能比 fputc 长
int putchar(int c);           // getc(c, stdout)
</code></span></code></pre>
<h3 id="57-io">5.7 每次一行 I/O</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
char *fgets(      char *restrict buf, int n, FILE *restrict fp); // 成功 buf，失败 NULL
int   fputs(const char *restrict str,        FILE *restrict fp); // 成功 非负，失败 EOF

// char *gets(char *buf);    // 返回值同 fgets，已经废弃，请不要使用
int   puts(cosnt char *str); // 返回值同 fputs，puts 会在末尾添加一个 换行符，请尽量不适用
</code></span></code></pre>
<h3 id="59-io">5.9 二进制 I/O</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">size_t fread(       void *ptr, size_t size, size_t nobj, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream);
</code></span></code></pre>
<h3 id="510">5.10 定位流</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
long ftell(FILE *fp);   // 返回当前文件位置指示
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);

// off_t 可以将被定义为长于32位
off_t ftello(FILE *fp);
int fseeko(FILE *fp, off_t offset, int whence); 

int fgetpos(FILE *restrict fp,       fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *restrict pos)
</code></span></code></pre>
<h2 id="_1">第六章 系统数据文件和信息</h2>
<h3 id="62">6.2 口令文件</h3>
<p>linux：/etc/passwd</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">用户名:加密口令:数值用户ID:数值组ID:注释字段:初始工作目录:初始shell
root:x:0:0:root:/root:/bin/bash
zhouwei43:x:4201:4202::/data1/zhouwei43:/bin/bash
</code></span></code></pre>
<p>阻止任何人登录一个特定的系统，可以设置初始 shell 为：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">/dev/null
/bin/false
/bin/true
</code></span></code></pre>
<p>vipm 命令可以用于修改口令文件</p>
<h3 id="63">6.3 阴影口令</h3>
<p>linux：/etc/shadow</p>
<p>是经过单向加密的口令副本</p>
<h3 id="64">6.4 组文件</h3>
<p>linux：/etc/group</p>
<h3 id="67">6.7 其他组</h3>
<p>口令：/etc/passwd  </p>
<p>组：/etc/group  </p>
<p>阴影：/etc/shadow </p>
<p>主机：/etc/hosts  </p>
<p>网络：/etc/networks </p>
<p>协议：/etc/protocols</p>
<p>服务：/etc/services </p>
<h2 id="_2">第七章 进程环境</h2>
<h3 id="73">7.3 进程终止</h3>
<h4 id="exit">exit</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdlib.h&gt; /* exit 和 _Exit 由 ISO C 说明 */

/* exit() 用来正常终结目前进程的执行，
并把参数 status 返回给父进程，
而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。*/
void exit(int status);

/* _Exit() 的效果等同下面的 _exit() */
void _Exit(int status);

#include &lt;unistd.h&gt; /* _exit 由 POSIX.1 说明 */

/* _exit() 用来立刻结束目前进程的执行，
并把参数status返回给父进程，并关闭未关闭的文件。
此函数调用后不会返回，并且会传递 SIGCHLD 信号给父进程，父进程可以由 wait 函数取得子进程结束状态。
_exit() 不会处理标准 I/O 缓冲区，如要更新缓冲区请使用 exit() */
void _exit(int status);
</code></span></code></pre>
<h4 id="atexit">atexit</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdlib.h&gt;

/** @brief  atexit() 用来设置一个程序正常结束前调用的函数。
 *  @param  当程序通过调用 exit() 或从 main 中返回时，
 *             参数 func 所指定的函数会先被调用，
 *             然后才真正由 exit() 结束程序。
 *  @return 如果执行成功则返回 0，
 *             否则返回-1，失败原因存于 errno 中。
 */
int atexit(void (*func)(void));
/* 注意：终止处理程序每登记一次，就会被调用一次。 */
</code></span></code></pre>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include&lt;stdlib.h&gt;

void my_exit(void)
{
    printf(“before exit () !\n”);
}

int main()
{
    atexit(my_exit);
    exit(0);
}
</code></span></code></pre>
<h3 id="75">7.5 环境表</h3>
<p>environ 全局环境变量指针</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">extern char **environ;

/*
              环境表
environ ----&gt; item1 ----&gt; &quot;Home=/home&quot;
              item2 ----&gt; &quot;PATH=:/bin:/usr/bin&quot;
              item3 ----&gt; &quot;SHELL=/bin/bash&quot;
              ......
              NULL
*/
</code></span></code></pre>
<p>getenv 和 putenv 函数访问特定的环境变量</p>
<h3 id="76-c">7.6 C 程序运行时的内存结构</h3>
<p><strong>低地址</strong> = TASK_SIZE</p>
<ul>
<li>内核空间 Kernel space</li>
<li>栈 Stack</li>
<li>堆 Heap</li>
<li>BSS Segment</li>
<li>通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。</li>
<li>数据段 Data Segment</li>
<li>通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。</li>
<li>代码段 Text Segment</li>
<li>通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定，并且内存区域通常属于只读, 某些架构也允许代码段为可写，即允许修改程序。在代码段中，也有可能包含一些只读的常数变量，例如字符串常量等。程序段为程序代码在内存中的映射。一个程序可以在内存中有多个副本。 </li>
</ul>
<p><strong>高地址</strong></p>
<h3 id="78">7.8 存储器分配</h3>
<h4 id="1_1">1）从堆上申请内存</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdlib.h&gt;

void *malloc(size_t size);

/* 在利用 calloc() 配置内存时会将内存内容初始化为0。*/
void *calloc(size_t nobj, size_t size);

/* 早期版本允许调用 realloc 分配上次 malloc，calloc， realloc 调用以来所释放的块。
它利用 malloc 的搜索策略，实现实现存储器紧缩，
但很多平台都不支持这种操作，不赞成也不应该使用这种操作。*/
void *realloc(void *ptr, size_t newsize);

/* 参数 ptr 为指向先前由 malloc()、calloc()或realloc() 所返回的内存指针。
若参数 ptr 所指的内存空间已被收回或是未知的内存地址，则调用free()可能会有无法预期的情况发生。
若参数 ptr 为 NULL，则 free() 不会有任何作用。*/
void free(void *ptr);
</code></span></code></pre>
<ul>
<li>用户进程</li>
<li>调用 malloc / free 使用 glibc 库来使用堆内存：free 只是把物理页面还给 glibc 而 glibc 采用一定的策略，把物理页面还给操作系统。</li>
<li>GLIBC 库</li>
<li>使用系统调用：sbork / brk 移动进 heap 区的最高线性地址的值，以此来调整进程 heap 区的大小，但是这时并没有得到真正的物理页面。</li>
<li>Linux 内核</li>
<li>内核在用户任务需要访问某段内存时，才为该段内存分配物理页面，并在任务的页表中建立映射。</li>
</ul>
<p><strong>为什么在 malloc 时，要求输入内存区域大小，而在 free 时不需要输入内存区域大小？</strong></p>
<p>在 (p-4) 这个地址上就记录着 malloc 空间的大小，glibc 在 free 这块内存的时候，可以在 (p-4) 这个地址上获取分配内存的大小。</p>
<h4 id="2">2）在栈上申请内存</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;alloca.h&gt;

void * __cdecl alloca(size_t);
</code></span></code></pre>
<p>在调用 alloca 的函数返回的时候, 它分配的内存会自动释放。也就是说，用 alloca 分配的内存在栈上。</p>
<ul>
<li>alloca <strong>不具可移植性</strong>，而且在没有传统堆栈的机器上很难实现。</li>
<li>当它的返回值直接传入另一个函数时会带来问题，因为他分配在栈上。</li>
<li>由于这些原因，alloca 不宜使用在必须广泛移植的程序中，不管它可能多么有用。</li>
<li><strong>既然 C99 支持变长数组(VLA)，它可以用来更好的完成 alloca() 以前的任务。</strong></li>
</ul>
<p>示例：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">int main(void)
{
    int *p = (int *)alloca(sizeof(int)*10);
    free(p);  //此时不能用free()去释放，会导致错误
    return 0;
}
</code></span></code></pre>
<h4 id="3-mmp">3）内存映射 mmp</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;
#include &lt;sys/mman.h&gt;

/** @brief  mmap()用来将某个文件内容映射到内存中，对该内存区域的存取即是直接对该文件内容的读写。
 *  @param  start 指向欲对应的内存起始地址，通常设为NULL，代表让系统自动选定地址，对应成功后该地址会返回。
 *  @param  length 代表将文件中多大的部分对应到内存。
 *  @param  prot 代表映射区域的保护方式
 *            PROT_EXEC  映射区域可被执行
 *            PROT_READ  映射区域可被读取
 *            PROT_WRITE 映射区域可被写入
 *            PROT_NONE  映射区域不能存取
 *  @param  flags 
 *            MAP_FIXED 如果参数start所指的地址无法成功建立映射时，则放弃映射，不对地址做修正。
 *               通常不鼓励用此旗标。
 *            MAP_SHARED 对映射区域的写入数据会复制回文件内，而且允许其他映射该文件的进程共享。
 *            MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制，即私人的“写入时复制”（copy on write）对此区域作的任何修改都不会写回原来的文件内容。
 *            MAP_ANONYMOUS 建立匿名映射。此时会忽略参数fd，不涉及文件，而且映射区域无法和其他进程共享。
 *            MAP_DENYWRITE 只允许对映射区域的写入操作，其他对文件直接写入的操作将会被拒绝。
 *            MAP_LOCKED 将映射区域锁定住，这表示该区域不会被置换（swap）。
 *  @param  fd open()返回的文件描述词，代表欲映射到内存的文件。
 *  @param  offset 文件映射的偏移量，通常设置为0，代表从文件最前方开始对应，offset必须是分页大小的整数倍。
 *  @return 若映射成功则返回映射区的内存起始地址，
 *             否则返回 MAP_FAILED(－1)，错误原因存于 errno 中。
 *             EBADF 参数fd 不是有效的文件描述词。
 *             EACCES 存取权限有误。
 *               如果是 MAP_PRIVATE 情况下文件必须可读，使用 MAP_SHARED 则要有PROT_WRITE以及该文件要能写入。
 *             EINVAL 参数start、length 或offset有一个不合法。
 *             EAGAIN 文件被锁住，或是有太多内存被锁住。
 *             ENOMEM 内存不足。
 */
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

/** @fn int munmap(void *start, size_t length)
 *  @brief  munmap() 用来取消参数 start 所指的映射内存起始地址。
 *  @param  参数 length 则是欲取消的内存大小。
 *  @return 如果解除映射成功则返回0，
 *             否则返回－1，错误原因存于 errno 中。
 *             EINVAL start 或 length 不合法。
 */
int munmap(void *start, size_t length);
</code></span></code></pre>
<p>当进程结束或利用 exec 相关函数来执行其他程序时，映射内存会自动解除，但关闭对应的文件描述时不会解除映射。</p>
<p><strong>在调用 mmap() 时必须要指定 MAP<em>SHARED 或 MAP</em>PRIVATE。</strong></p>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/mman.h&gt;

int main(int argc, char *argv[])
{
    int fd;
    void *start;
    struct stat sb;

    fd = open(&quot;/etc/passwd&quot;, O_RDONLY); /* 打开/etc/passwd */
    fstat(fd, &amp;sb); /* 取得文件大小 */
    start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (start == MAP_FAILED) /* 判断是否映射成功 */
    {
        return -1;
    }
    printf(&quot;%s&quot;, (char *)start);
    munmap(start, sb.st_size); /* 解除映射 */
    close(fd);
    return 0;
}
</code></span></code></pre>
<h3 id="79">7.9 环境变量</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdlib.h&gt;

/** @brief  用来取得参数 name 环境变量的内容。
 *  @param  name 为环境变量的名称，如果该变量存在则会返回指向该内容的指针。环境变量的格式为 name＝value。
 *  @return 执行成功则返回指向该内容的指针，找不到符合的环境变量名称则返回 NULL。
 */
char *getnev(const char *name);

/** @brief  用来改变或增加环境变量的内容。
 *  @param  string 的格式为 name＝value，
 *             如果该环境变量原先存在，则变量内容会依参数 string 改变，
 *             否则此参数内容会成为新的环境变量。
 *  @return 执行成功则返回0，有错误发生则返回-1。
 *             ENOMEM 内存不足，无法配置新的环境变量空间。
 */
int putenv(char *str);

/** @brief  用来改变或增加环境变量的内容。
 *  @param  name 为环境变量的名称
 *  @param  value 则为变量内容
 *  @param  rewrite 用来决定是否要改变已存在的环境变量。
 *             如果 rewrite 不为 0，而该环境变量原已有内容，则原内容会被改为参数 value 所指的变量内容。
 *             如果 rewrite 为 0，且该环境变量已有内容，则参数 value 会被忽略。
 *  @return 执行成功则返回0，有错误发生则返回-1。
 *             ENOMEM 内存不足，无法配置新的环境变量空间。
 */
int setenv(const char *name, const char *value, int rewrite);
int unsetenv(const char *name);
</code></span></code></pre>
<h2 id="_3">第八章 进程控制</h2>
<h3 id="82">8.2 进程标识符</h3>
<p>进程ID为0：swapper 交换进程，用于进程调度。</p>
<p>进程ID为1：init 进程，读取执行系统有关的初始化文件。</p>
<p>UNIX 的早期版本为：/etc/init，较新版本为：/sbin/init</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/etc/rc*
/etc/inittab
/etc/init.d
</code></span></code></pre>
<p>进程ID为2：pagedaemon 页守护进程，负责支持虚拟存储系统的分页操作。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

pid_t getpid(void);         /* 调用进程的进程 ID */
pid_t getppid(void);        /* 调用进程的父进程 ID */

uid_t getuid(void);         /* 调用进程的实际用户 ID */
uid_t geteuid(void);        /* 调用进程的有效用户 ID */

gid_t getgid(void);         /* 调用进程的实际组 ID */
gid_t getegid(void);        /* 调用进程的有效组 ID */

/* 用来取得参数pid 指定进程所属的组识别码。如果参数pid为0，则会取得目前进程的组识别码。*/
pid_t getpgid(pid_t pid); 
/* 用来取得目前进程所属的组识别码。此函数相当于调用getpgid(0) */
pid_t getpgrp(void);
</code></span></code></pre>
<h3 id="83-fork">8.3 fork 函数</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief  创建一个新的进程
 *  @return pid_t是一个宏定义，其实质是int被定义在sys/types.h中。
 *          负值：创建子进程失败。
 *              失败的两个主要原因：
 *              1. 系统中已经有太多进程。
 *              2. 该实际用户ID的进程总数超过了系统限制。
 *          零：返回到新创建的子进程。
 *          正值：返回父进程或调用者。该值包含新创建的子进程的进程ID。
 */
pid_t fork(void);
/* Linux 使用 copy-on-write(COW)技术，
只有当其中一进程试图修改欲复制的空间时才会做真正的复制动作，
由于这些继承的信息是复制而来，并非指相同的内存空间，
因此子进程对这些变量的修改和父进程并不会同步。 */

/* vfork保证子进程先运行，在他调用exec或exit之后父进程才可能被调度运行。*/
/* vfork 并不将父进程的地址空间完全复制到子进程中。子进程在父进程的地址空间中运行。*/
pid_t vfork(void);
</code></span></code></pre>
<ul>
<li>Linux 下是如何执行新程序的，每当有进程认为自己不能为系统和用户做出任何贡献了，他就可以发挥最后一点余热，调用任何一个 exec，让自己以新的面貌重生。</li>
<li>或者，更普遍的情况是，如果一个进程想执行另一个程序，它就可以 fork 出一个新进程，然后调用任何一个exec，这样看起来就好像通过执行应用程序而产生了一个新进程一样。</li>
<li>Linux 专门为其作了优化，我们已经知道，fork 会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去，这些拷贝的动作很消耗时间，而如果 fork 完之后我们马上就调用 exec，这些辛辛苦苦拷贝来的东西又会被立刻抹掉，这看起来非常不划算，于是人们设计了一种&rdquo;写时拷贝（copy-on-write）&rdquo;技术，使得 fork 结束后并不立刻复制父进程的内容，而是到了真正实用的时候才复制，这样如果下一条语句是exec，它就不会白白作无用功了，也就提高了效率。</li>
</ul>
<p>useage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char * argv[])
{
    char write_str[] = &quot;write_str info.\n&quot;;
    char fwrite_str[] = &quot;fwrite_str info.\n&quot;;
    pid_t pid;

    // if (write(fileno(stdout), write_str, sizeof(write_str)-1) != sizeof(write_str)-1)
    /* write 是不带缓冲区的 IO，结果会直接写到目标中 */
    if (write(STDOUT_FILENO, write_str, sizeof(write_str)-1) != sizeof(write_str)-1) 
    {
        printf(&quot;write error.\n&quot;);
        exit(1);
    }
    /* fwrite 和 printf 是带缓冲区的 标准IO，
        直接执行（连接到终端设备）时是行缓冲，重定向到文件时是全缓冲 */
    if (fwrite(fwrite_str, sizeof(fwrite_str)-1, 1, stdout) != 1) 
    {
        printf(&quot;fwrite error.\n&quot;);
        exit(1);
    }

    printf(&quot;before fork.\n&quot;);

    fflush(stdout);
    if ((pid = fork()) &lt; 0) 
    {
        printf(&quot;fork error.\n&quot;);
        exit(1);
    }
    else if (pid == 0) 
    {
        printf(&quot;child process.\n&quot;);
        exit(0);
    }
    else {
        printf(&quot;parent process. pid of child: %d\n&quot;, pid);
        exit(0);
    }
    return 0;
}
</code></span></code></pre>
<p>fwrite 和 printf 是带缓冲区的 标准IO，直接执行（连接到终端设备）时是行缓冲，重定向到文件时是全缓冲，在 fork 时，其信息还保存在缓冲区中，父进程会将其数据空间复制到子进程，当每个进程结束时，才会冲洗其缓冲区，故父进程和子进程都会输出 fork 之前 fwrite 和 printf 缓冲区的内容。</p>
<p>result：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ ./a.out
write_str info.
fwrite_str info.
before fork.
parent process. pid of child: 21085
child process.
$ ./a.out &gt; temp.out
$ cat temp.out
write_str info.
fwrite_str info. 
before fork.
parent process. pid of child: 21087
fwrite_str info. # 缓冲区副本
before fork.     # 缓冲区副本
child process.
</code></span></code></pre>
<p>test：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">if ((pid = fork() /* vfork() */ ) &lt; 0) {
    printf(&quot;fork error.\n&quot;);
    exit(1);
}
else if (pid == 0) {
    printf(&quot;child process.\n&quot;);
    /* exit(1) / _exit(1) */
}
else {
    printf(&quot;parent process. pid of child: %d\n&quot;, pid);
}
</code></span></code></pre>
<h5 id="forkexit">fork,exit</h5>
<p>write<em>str info.<br />
fwrite</em>str info.<br />
before fork.<br />
parent process. pid of child: 27205<br />
fwrite_str info.<br />
before fork.<br />
child process.</p>
<h5 id="fork_exit">fork,_exit</h5>
<p>write<em>str info.<br />
/<em> 子进程结束，没有刷新子进程自己的缓冲区，因此没有任何打印信息 </em>/<br />
fwrite</em>str info.<br />
before fork.<br />
parent process. pid of child: 27232</p>
<h5 id="vforkexit">vfork,exit</h5>
<p>cat temp.out<br />
write<em>str info.<br />
fwrite</em>str info.<br />
before fork.<br />
child process.<br />
/<em> 子进程结束，前面的内容由子进程刷新父进程的缓冲区打印（在子进程结束前，子进程共享父进程的数据空间） </em>/<br />
parent process. pid of child: 27247</p>
<h5 id="vfork_exit">vfork,_exit</h5>
<p>cat temp.out<br />
write<em>str info.<br />
fwrite</em>str info.<br />
before fork. <br />
/<em> 子进程结束，没有刷新父进程的缓冲区，上面和下面的内容都由父进程刷新父进程的缓冲区打印 </em>/<br />
child process.<br />
parent process. pid of child: 27279</p>
<h5 id="_4">文件共享</h5>
<p>父进程的所有打开文件描述符都被复制到子进程中：</p>
<ul>
<li>类似于执行了dup函数。</li>
<li><strong>父进程和子进程共享同一个文件偏移量。</strong></li>
</ul>
<p>fork 之后处理文件描述符的方法：</p>
<ol>
<li>父进程等待子进程完成。</li>
<li>父进程和子进程各自执行不同的程序段，关闭他们不需要的文件描述符。</li>
</ol>
<h3 id="85-exit">8.5 exit 函数</h3>
<h4 id="_5">正常终止</h4>
<ol>
<li>main 函数内执行 return 语句。</li>
<li>调用 exit 函数。</li>
<li>调用 <em>exit 或 </em>Exit 函数。（<span style="overflow-x: auto; max-width:100%; display:inline;"><code>_exit(2)</code></span>是一个系统调用）</li>
<li>进程的最后一个线程在其启动例程中执行返回语句。但是，该线程的返回值不会用作该进程的返回值，当最后一个线程返回时，该进程以终止状态 0 返回。</li>
<li>最后一个线程调用 pthread_exit 函数，与 4 相同。</li>
</ol>
<h4 id="_6">异常终止</h4>
<ol>
<li>调用 abort。</li>
<li>当进程接收到某些信号时。</li>
<li>最后一个线程对取消请求做出响应。</li>
</ol>
<h3 id="86-wait-waitpid">8.6 wait 和 waitpid</h3>
<p>等待子进程中断或结束</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include&lt;sys/types.h&gt;
#include&lt;sys/wait.h&gt;

/** @fn pid_t wait(int * status)
 *  @brief  wait()会暂时停止目前进程的执行，直到有信号来到或子进程结束。
 *             如果在调用wait()时子进程已经结束，则wait()会立即返回子进程结束状态值。
 *             子进程的结束状态值会由参数status返回，而子进程的进程识别码也会返回。
 *  @param  status 如果不在意结束状态值，status可以设成NULL。子进程的结束状态值请参考waitpid()。
 *  @return pid_t是一个宏定义，其实质是int被定义在sys/types.h中。
 *             如果执行成功则返回子进程识别码(PID)，
 *             如果有错误发生则返回-1。失败原因存于errno中。
 */
pid_t wait(int *status);

/** @fn pid_t waitpid(pid_t pid, int *status, int options)
 *  @brief          waitpid()会暂时停止目前进程的执行，直到有信号来到或子进程结束。
 *                     如果在调用wait()时子进程已经结束，则wait()会立即返回子进程结束状态值。
 *                     子进程的结束状态值会由参数status返回，而子进程的进程识别码也会返回。
 *  @param pid      参数pid为欲等待的子进程识别码，其他数值意义如下:
 *                      pid == -1 等待任一子进程
 *                      pid &gt; 0 等待其进程ID与pid相同的进程
 *                      pid = 0 等待其组ID与调用进程组ID相同的任一进程
 *                      pid &lt; -1 等待其组ID等于pid绝对值的任一进程
 *  @param status   如果不在意结束状态值，则参数status可以设成NULL。
 *  @param options  参数option可以为0 或下面的OR组合:
 *                      WNOHANG       如果没有任何已经结束的子进程则马上返回，不予以等待。
 *                      WUNTRACED     如果子进程进入暂停执行情况则马上返回，但结束状态不予以理会。
 *                                       子进程的结束状态返回后存于status，底下有几个宏可判别结束情况
 *                      WIFEXITED     如果子进程正常结束则为非0值。
 *                      WEXITSTATUS   取得子进程exit()返回的结束代码，
 *                                       一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
 *                      WIFSIGNALED   如果子进程是因为信号而结束则此宏值为真
 *                      WTERMSIG      取得子进程因信号而中止的信号代码，
 *                                       一般会先用WIFSIGNALED 来判断后才使用此宏。
 *                      WIFSTOPPED    如果子进程处于暂停执行情况则此宏值为真。
 *                                       一般只有使用WUNTRACED 时才会有此情况。
 *                      WSTOPSIG      取得引发子进程暂停的信号代码，
 *                                       一般会先用WIFSTOPPED 来判断后才使用此宏。
 *  @return pid_t   是一个宏定义，其实质是int被定义在sys/types.h中。
 *                     如果执行成功则返回子进程识别码(PID)，
 *                     如果有错误发生则返回-1。失败原因存于errno中。
 */
pid_t waitpid(pid_t pid, int *status, int options);
</code></span></code></pre>
<p>waitpid：</p>
<ol>
<li>可等待一个特定的进程。</li>
<li>提供了一个非阻塞的 wait。</li>
<li>支持作业控制。</li>
</ol>
<h3 id="810-exec">8.10 exec 族</h3>
<p>在 Linux 中，并不存在一个 exec() 的函数形式，exec 指的是一组函数，一共有6个，分别是：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;
extern char **environ;

int execl (const char *path, const char *arg, ... /* (char*)0 */);
int execv (const char *path, char *const argv[]);

/* 从PATH环境变量中查找文件并执行 */
int execlp(const char *file, const char *arg, ... /* (char*)0 */);
int execvp(const char *file, char *const argv[]);

int execle(const char *path, const char *arg, ... /* (char*)0, char *const envp[] */);
int execve(const char *path, char *const argv[], char *const envp[]);

</code></span></code></pre>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>execlp                        execl                      execle
|                             |                          |
| argv                        | argv                     | argv
|                             |                          |
execvp ----尝试每个PATH前缀----&gt; execv ----使用environ----&gt; execve(系统调用) 
</code></span></code></pre>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">char *const ps_argv[] = {&quot;ps&quot;,&quot;-o&quot;, &quot;pid,ppid,pgrp,session,tpgid,comm&quot;, NULL}; 
char *const ps_envp[] = {&quot;PATH=/bin:/usr/bin&quot;, &quot;TERM=console&quot;, NULL}; 

execl (&quot;/bin/ps&quot;, &quot;ps&quot;, &quot;-o&quot;, &quot;pid,ppid,pgrp,session,tpgid,comm&quot;, NULL); 
execv (&quot;/bin/ps&quot;, ps_argv); 

execle(&quot;/bin/ps&quot;, &quot;ps&quot;, &quot;-o&quot;, &quot;pid,ppid,pgrp,session,tpgid,comm&quot;, NULL, ps_envp); 
execve(&quot;/bin/ps&quot;, ps_argv, ps_envp); 

execlp(&quot;ps&quot;, &quot;ps&quot;, &quot;-o&quot;, &quot;pid,ppid,pgrp,session,tpgid,comm&quot;, NULL); 
execvp(&quot;ps&quot;, ps_argv);
</code></span></code></pre>
<p>其中只有execve()是真正意义上的系统调用，其它都是在此基础上经过包装的库函数。</p>
<p>如果执行成功则函数不会返回，执行失败则直接返回-1，失败原因存于errno 中。</p>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>错误代码</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>EACCES</td>
<td>1.欲执行的文件不具有用户可执行的权限。<br/>2.欲执行的文件所属的文件系统是以noexec 方式挂上。<br/>3.欲执行的文件或script翻译器非一般文件。</td>
</tr>
<tr>
<td>EPERM</td>
<td>1.进程处于被追踪模式，执行者并不具有root权限，欲执行的文件具有SUID 或SGID 位。<br/>2.欲执行的文件所属的文件系统是以nosuid方式挂上，欲执行的文件具有SUID 或SGID 位元，但执行者并不具有root权限。</td>
</tr>
<tr>
<td>E2BIG</td>
<td>参数数组过大</td>
</tr>
<tr>
<td>ENOEXEC</td>
<td>无法判断欲执行文件的执行文件格式，有可能是格式错误或无法在此平台执行。</td>
</tr>
<tr>
<td>EFAULT</td>
<td>参数filename所指的字符串地址超出可存取空间范围。</td>
</tr>
<tr>
<td>ENAMETOOLONG</td>
<td>参数filename所指的字符串太长。</td>
</tr>
<tr>
<td>ENOENT</td>
<td>参数filename字符串所指定的文件不存在。</td>
</tr>
<tr>
<td>ENOMEM</td>
<td>核心内存不足</td>
</tr>
<tr>
<td>ENOTDIR</td>
<td>参数filename字符串所包含的目录路径并非有效目录</td>
</tr>
<tr>
<td>EACCES</td>
<td>参数filename字符串所包含的目录路径无法存取，权限不足</td>
</tr>
<tr>
<td>ELOOP</td>
<td>过多的符号连接</td>
</tr>
<tr>
<td>ETXTBUSY</td>
<td>欲执行的文件已被其他进程打开而且正把数据写入该文件中</td>
</tr>
<tr>
<td>EIO</td>
<td>I/O存取错误</td>
</tr>
<tr>
<td>ENFILE</td>
<td>已达到系统所允许的打开文件总数。</td>
</tr>
<tr>
<td>EMFILE</td>
<td>已达到系统所允许单一进程所能打开的文件总数。</td>
</tr>
<tr>
<td>EINVAL</td>
<td>欲执行文件的ELF执行格式不只一个PT_INTERP节区</td>
</tr>
<tr>
<td>EISDIR</td>
<td>ELF翻译器为一目录</td>
</tr>
<tr>
<td>ELIBBAD</td>
<td>ELF翻译器有问题</td>
</tr>
</tbody>
</table></div>
<h3 id="811-id-id">8.11 更改用户 ID 和更改组 ID</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;
int setuid(uid_t uid);
int setgid(gid_t gid);

// 交换实际用户ID和有效用户ID
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, uid_t euid);

int seteuid(uid_t uid);
int setegid(gid_t gid);
</code></span></code></pre>
<h3 id="813-system">8.13 system</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#inlcude &lt;stdlib.h&gt;
int system(const char *cmdstring);
</code></span></code></pre>
<h3 id="814">8.14 会计进程</h3>
<h2 id="_7">第十章 信号</h2>
<blockquote class="content-quote">
<p>拓展资料：https://blog.csdn.net/feather<em>wch/article/details/50790292?utm</em>medium=distribute.pc<em>relevant.none-task-blog-2~default~baidujs</em>title~default-0.control&amp;spm=1001.2101.3001.4242</p>
</blockquote>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">&lt;signal.h&gt;
SIGABRT // 夭折信号，abort 函数产生
SIGALRM // 闹钟信号，alarm 函数设定的定时器超时后产生该信号
SIGINT  // 中断信号，Ctrl + C
SIGSEGV // 硬件异常信号：除数为0，无效的内存引用

SIGKILL // 不能忽略，不能捕捉
SIGSTOP // 不能忽略，不能捕捉
</code></span></code></pre>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th></th>
<th>信号</th>
<th>说明</th>
<th>默认动作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>SIGABRT</td>
<td>异常终止(abort)</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>SIGALRM</td>
<td>定时器超时(alarm)</td>
<td></td>
</tr>
<tr>
<td></td>
<td>SIGBUS</td>
<td>硬件故障</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>SIGCANCEL</td>
<td>线程库内部使用</td>
<td>忽略</td>
</tr>
<tr>
<td>5</td>
<td>SIGCHLD</td>
<td>子进程状态改变</td>
<td>忽略</td>
</tr>
<tr>
<td>6</td>
<td>SIGCONT</td>
<td>使暂停进程继续</td>
<td>继续/忽略</td>
</tr>
<tr>
<td>7</td>
<td>SIGEMT</td>
<td>硬件故障</td>
<td></td>
</tr>
<tr>
<td>8</td>
<td>SIGFPE</td>
<td>算术异常</td>
<td></td>
</tr>
<tr>
<td>9</td>
<td>SIGFEEZE</td>
<td>检查点冻结</td>
<td>忽略</td>
</tr>
<tr>
<td>10</td>
<td>SIGHUP</td>
<td>连接断开</td>
<td></td>
</tr>
<tr>
<td>11</td>
<td>SIGILL</td>
<td>非法硬件指令</td>
<td></td>
</tr>
<tr>
<td>12</td>
<td>SIGINFO</td>
<td>键盘状态请求</td>
<td>忽略</td>
</tr>
<tr>
<td>13</td>
<td>SIGINT</td>
<td>终端中断符</td>
<td></td>
</tr>
<tr>
<td>14</td>
<td>SIGIO</td>
<td>异步IO</td>
<td>终止/忽略</td>
</tr>
<tr>
<td>5</td>
<td>sIGIOT</td>
<td>硬件故障</td>
<td></td>
</tr>
<tr>
<td>16</td>
<td>SIGJVM1</td>
<td>Java虚拟机内部使用</td>
<td>忽略</td>
</tr>
<tr>
<td>17</td>
<td>SIGJVM2</td>
<td>Java 虚拟机内部使用</td>
<td>忽略</td>
</tr>
<tr>
<td>18</td>
<td><a style="color:red">SIGKILL</a></td>
<td><a style="color:red">终止，不可忽略捕捉</a></td>
<td></td>
</tr>
<tr>
<td>19</td>
<td>SIGLOST</td>
<td>资源丢失</td>
<td></td>
</tr>
<tr>
<td>20</td>
<td>SIGLWP</td>
<td>线程库内部使用</td>
<td>终止/忽略</td>
</tr>
<tr>
<td>21</td>
<td><a style="color:blue">SIGPIPE</a></td>
<td><a style="color:blue">写至无读进程的管道</a></td>
<td></td>
</tr>
<tr>
<td>22</td>
<td>SIGPOLL</td>
<td>可轮询事件(poll)</td>
<td></td>
</tr>
<tr>
<td>23</td>
<td>SIGPROF</td>
<td>梗概时间超时(setitimer)</td>
<td></td>
</tr>
<tr>
<td>24</td>
<td>SIGPWR</td>
<td>电源失效/重启动</td>
<td>终止/忽略</td>
</tr>
<tr>
<td>25</td>
<td>SIGQUIT</td>
<td>终端退出符</td>
<td></td>
</tr>
<tr>
<td>26</td>
<td>SIGSEGV</td>
<td>无效内存引用</td>
<td></td>
</tr>
<tr>
<td>27</td>
<td>SIGSTKFLT</td>
<td>协处理器栈故障</td>
<td></td>
</tr>
<tr>
<td>28</td>
<td><a style="color:red">SIGSTOP</a></td>
<td><a style="color:red">停止，不可忽略捕捉</a></td>
<td></td>
</tr>
<tr>
<td>29</td>
<td>SIGSYS</td>
<td>无效系统调用</td>
<td></td>
</tr>
<tr>
<td>30</td>
<td>SIGTERM</td>
<td>停止</td>
<td></td>
</tr>
<tr>
<td>31</td>
<td>SIGTHAW</td>
<td>检查点解冻</td>
<td>忽略</td>
</tr>
<tr>
<td>32</td>
<td>SIGTHR</td>
<td>线程库内部使用</td>
<td>忽略</td>
</tr>
<tr>
<td>33</td>
<td>SIGTRAP</td>
<td>硬件故障</td>
<td></td>
</tr>
<tr>
<td>34</td>
<td>SIGTSTP</td>
<td>终端停止符</td>
<td></td>
</tr>
<tr>
<td>35</td>
<td>SIGTTIN</td>
<td>后台读控制tty</td>
<td></td>
</tr>
<tr>
<td>36</td>
<td>SIGTTOU</td>
<td>后台写向控制tty</td>
<td></td>
</tr>
<tr>
<td>37</td>
<td>SIGURG</td>
<td>紧急情况(套接字)</td>
<td>忽略</td>
</tr>
<tr>
<td>38</td>
<td>SIGUSR1</td>
<td>用户定义信号</td>
<td></td>
</tr>
<tr>
<td>39</td>
<td>SIGUSR2</td>
<td>用户定义信号</td>
<td></td>
</tr>
<tr>
<td>40</td>
<td>SIGVTALRM</td>
<td>虚拟时间闹钟(setitimer)</td>
<td></td>
</tr>
<tr>
<td>41</td>
<td>SIGWAITING</td>
<td>线程库内部使用</td>
<td>忽略</td>
</tr>
<tr>
<td>42</td>
<td>SIGWIINCH</td>
<td>终端窗口大小改变</td>
<td>忽略</td>
</tr>
<tr>
<td>43</td>
<td>SIGXCPU</td>
<td>超过CPU限制(setrlimit)</td>
<td></td>
</tr>
<tr>
<td>44</td>
<td>SIGXFSZ</td>
<td>超过文件 长度限制(setrlimit)</td>
<td></td>
</tr>
<tr>
<td>45</td>
<td>SIGSXRES</td>
<td>超过资源控制</td>
<td>忽略</td>
</tr>
</tbody>
</table></div>
<p>信号的处理方式：</p>
<ul>
<li><strong>忽略此信号。</strong>大多数信号都可以采用这种方式，但是有两种信号绝不能忽略。他们是 SIGKILL，SIGSTOP，他们向内核和超级用户提供了使进程终止或停止的可靠方法。</li>
<li><strong>捕捉信号。</strong>通知内核在某种信号发生时，调用一个用户函数。<ul>
<li>例如，捕捉 SIGCHLD 信号，表示一个子进程终止，所以此信号的捕捉函数可以调用 waitpid 以取得该子进程的进程 ID 以及它的终止状态。</li>
<li>例如，创建了临时文件，那么可能要为 SIGTREM 信号编写一个信号捕捉函数以清除临时文件。（SIGTREM 是终止信号，kill 命令传送的系统默认信号是终止信号）。注意不能捕捉 SIGKILL 和 SIGSTOP 信号。</li>
</ul>
</li>
<li><strong>执行系统默认动作。</strong></li>
</ul>
<h3 id="103-signal">10.3 signal</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;signal&gt;

#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1

/** @brief 依参数 signo 指定的信号编号来设置该信号的处理函数。
 *         当指定的信号到达时就会跳转到参数 func 指定的函数执行。
 *         如果参数 func 不是函数指针，则必须是两个常数之一。
 *  @param signo    信号编号
 *  @param func     1. 函数指针
 *                  2. SIG_DFL 重设为核心预设的信号处理方式。
 *                  3. SIG_IGN 忽略信号。
 *  @return 成功返回以前的信号处理配置，出错返回 SIG_ERR
 */
void (*siganl(int signo, void (*func)(int)))(int);

typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
</code></span></code></pre>
<blockquote class="content-quote">
<p>在信号发生跳转到自定的 func 处理函数执行后，系统会自动将此处理函数换回原来系统预设的处理方式，如果要改变此操作请改用 sigaction()。</p>
</blockquote>
<h3 id="109-kill-raise">10.9 kill 和 raise</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;signal.h&gt;

/** @brief 用来等待文件描述词状态的改变。
 *  @param pid      pid&gt;0  将信号传给进程 ID 为 pid 的进程。
 *                  pid=0  将信号传给和目前进程相同进程组的所有进程
 *                  pid=-1 将信号广播传送给系统内所有的进程
 *                  pid&lt;0  将信号传给进程组识别码为 pid 绝对值的所有进程
 *  @param signo    信号编号
 *  @return 成功返回0，出错返回-1
 */
int kill(pid_t pid, int signo); // 将信号发送给进程或进程组
int raise(int signo); // 允许进程向自身发送信号，kill(getpid(), signo)
</code></span></code></pre>
<h3 id="1010-alarm-pause">10.10 alarm 和 pause</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;

/** @brief 用来设置信号 SIGALRM
 *  @param seconds  在经过参数 seconds 指定的秒数后信号 SIGALRM 传送给目前的进程。
 *                  若 seconds 为0，则之前设置的闹钟会被取消，并将剩下的时间返回。
 *  @return 返回之前闹钟的剩余秒数，如果之前未设闹钟则返回 0。
 */
unsigned int alarm(unsigned int seconds);

/** @brief pause()会令目前的进程暂停（进入睡眠状态），直到被信号(signal)所中断。
 *  @return 只返回-1。
 */
int pause(void);
</code></span></code></pre>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;

static void sig_alrm(int signo)
{
    /* nothing to do, just return to wake up the pause */
}

unsigned int sleep1(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return (seconds);
    alarm(seconds);     /* start the timer */
    pause();            /* next caught signal wakes us up */
    return (alarm(0));  /* turn off timer, return unslept time */
}
</code></span></code></pre>
<p>问题：</p>
<ol>
<li>如果在调用 sleep1 之前，调用者已经设置了闹钟，则它被 speel1 函数中的第一次 alarm 擦除。</li>
<li>该程序修改了对 SIGALRM 信号的配置。</li>
<li>第一次调用 alarm 和 pause 之间有一个竞争关系。</li>
</ol>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include    &lt;setjmp.h&gt;
#include    &lt;signal.h&gt;
#include    &lt;unistd.h&gt;

static jmp_buf  env_alrm;

static void
sig_alrm(int signo)
{
    longjmp(env_alrm, 1);
}

unsigned int sleep2(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return (seconds);
    if (setjmp(env_alrm) == 0) {
        alarm(seconds);     /* start the timer */
        pause();            /* next caught signal wakes us up */
    }
    return (alarm(0));      /* turn off timer, return unslept time */
}
</code></span></code></pre>
<h3 id="1011">10.11 信号集</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include&lt;signal.h&gt;

typedef struct { 
    unsigned long sig[_NSIG_WORDS]； 
} sigset_t;

int sigemptyset(sigset_t *set); // 将信号集初始化并清空。
int sigfillset(sigset_t *set); // 将信号集初始化，然后把所有的信号加入到此信号集里。

int sigaddset(sigset_t *set, int signo); // 将信号加入至信号集里。
int sigdelset(sigset_t *set, int signo); // 将信号从信号集里删除。
// 上面 4 个函数，执行成功则返回0，如果有错误则返回-1。

int sigismember(const sigset_t *set, int signo); // 测试某个信号是否已加入至信号集里
// 信号集已有该信号则返回1，没有则返回0。如果有错误则返回-1。
</code></span></code></pre>
<h3 id="1012">10.12</h3>
<h3 id="1013">10.13</h3>
<h3 id="1014-sigaction">10.14 sigaction</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;signal.h&gt;

struct sigaction
{
    void        (*sa_handler) (int); // 新的信号处理函数
    sigset_t    sa_mask;  // 设置在处理该信号时暂时将 sa_mask 指定的信号搁置。
    int         sa_flags; // 用来设置信号处理的其他相关操作，下列的数值可用。
    /* OR 运算（|）组合
        SA_NOCLDSTOP : 
            如果 signo 为 SIGCHLD，则当子进程暂停时并不会通知父进程。
        SA_ONESHOT/SA_RESETHAND : 
            当调用新的信号处理函数前，将此信号处理方式改为系统预设的方式。
        SA_RESTART : 
            被信号中断的系统调用会自行重启。
        SA_NOMASK/SA_NODEFER : 
            在处理此信号未结束前不理会此信号的再次到来。
    */
    void        (*sa_restorer) (void); // 此参数没有使用。
}

/** @brief 设置该信号的处理函数。
 *  @return 执行成功则返回0，如果有错误则返回-1。
 *      错误代码 
 *      EINVAL  参数 signo 不合法， 或是企图拦截 SIGKILL/SIGSTOPSIGKILL 信号
 *      EFAULT  参数 act，oldact 指针地址无法存取。
 *      EINTR   此调用被中断
 */
int sigaction(int signo, 
              const struct sigaction *act, 
                    struct sigaction *oldact);
</code></span></code></pre>
<h2 id="_8">第十一章 线程</h2>
<blockquote class="content-quote">
<p>注意：<span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread.h</code></span> 不是 ISO C 的内容，使用 gcc 编译的时候需要添加 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>-lpthread</code></span></p>
</blockquote>
<p>每个线程都包含有表示执行环境所必须的信息，其中包括：</p>
<ul>
<li>进程中标识线程的线程ID</li>
<li>一组寄存器的值</li>
<li>栈</li>
<li>调度优先级和策略</li>
<li>信号屏蔽字</li>
<li>errno 变量</li>
</ul>
<p>一个进程的所有信息对该进程的所有线程都是共享的，包括：</p>
<ul>
<li>可执行程序的代码</li>
<li>程序的全局内存</li>
<li>堆内存</li>
<li>栈</li>
<li>文件描述符</li>
</ul>
<h3 id="113">11.3 线程标识</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;
int pthread_equal(pthread_t tid1, pthread_t tid2); // 相等返回非0(true)，失败返回0(false)
pthread_t pthread_self(void); // 返回值：调用线程的线程 ID
</code></span></code></pre>
<h3 id="114">11.4 线程创建</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;
int pthread_create(      pthread_t      *restrict tidp, 
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void *), void *restrict arg);
// 成功返回0(true)，失败返回错误编号
</code></span></code></pre>
<h3 id="115">11.5 线程终止</h3>
<p>注意：进程中的任意线程调用了 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>exit</code></span>，<span style="overflow-x: auto; max-width:100%; display:inline;"><code>_Exit</code></span>，<span style="overflow-x: auto; max-width:100%; display:inline;"><code>_exit</code></span>，整个进程就会终止。</p>
<ol>
<li>线程启动历程中 return</li>
<li><span style="overflow-x: auto; max-width:100%; display:inline;"><code>void pthread_exit(void *rval_ptr);</code></span></li>
<li>被同进程中的其他线程取消</li>
</ol>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#incldue &lt;pthread.h&gt;
void pthread_exit(void *rval_ptr);

// 调用 pthread_join 之后，调用线程将会被阻塞，直到目标线程终止
int pthread_join(pthread_t thread, void **rval_ptr); // 成功返回0，失败返回错误编号
</code></span></code></pre>
<h4 id="_9">取消线程</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;
int pthread_cancel(pthread_t tid); // 成功返回0，失败返回错误编号
</code></span></code></pre>
<ul>
<li><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cancel</code></span> 会使得目标线程 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>tid</code></span>，表现得如同调用 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_exit(PTHREAD_CANCELED)</code></span></li>
<li>目标线程 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>tid</code></span> 可以选择忽略或者控制如何被取消。</li>
<li><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cancel</code></span> 并不等待线程终止，它仅提出请求。</li>
</ul>
<h4 id="_10">线程清理处理程序</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;
void pthread_cleacup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
</code></span></code></pre>
<p><strong>注意：<span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cleacup_push</code></span> 和 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cleanup_pop</code></span> 维护一个栈，调用顺序与注册顺序相反。</strong></p>
<p>当以下情况发生时，会调用 rtn 指向的函数，参数为 arg：</p>
<ul>
<li>调用 pthread_exit</li>
<li>相应取消请求</li>
<li>用非 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>execute != 0</code></span> 参数调用 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cleanup_pop</code></span><ul>
<li>用 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>execute == 0</code></span> 参数调用时，虽然不会执行清除函数 rtn，但是会删除上次 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pthread_cleacup_push</code></span> 建立的清楚程序。</li>
</ul>
</li>
</ul>
<h3 id="116">11.6 线程同步</h3>
<h4 id="1161">11.6.1 互斥量</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;

// arrt 设置为 NULL，使用默认属性初始化互斥量
// 成功返回0，失败返回错误编号
int pthread_mutex_init(     pthread_mutex_t     *restrict mutex,
                      const pthread_mutexarrt_t *restrict arrt); 

/* 
注意：
-   静态分配的互斥量，可以直接赋值 PTHREAD_MUTEX_INITIALIZER
-   动态分配的互斥量，需要用 `pthread_mutex_init` 初始化
-   使用动态分配的互斥量（如 malloc），**在释放内存前** 必须使用 `pthread_mutex_destroy`
*/
// 成功返回0，失败返回错误编号
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 成功返回0，失败返回错误编号，如果 mutex 已经上锁，会导致调用线程阻塞
int pthread_mutex_lock   (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);

/* 尝试获取互斥量 */
// 成功返回0，失败返回错误编号，如果 mutex 已经上锁，会失败，返回 EBUSY
int pthread_mutex_trylock(pthread_mutex_t *mutex);

/* 带超时的互斥量 */
#include &lt;pthread.h&gt;
#include &lt;time.h&gt;

// 成功返回0，失败返回错误编号
int pthread_mutex_timedlock(     pthread_mutex_t *restrict mutex,
                           const struct timespec *restrict tsptr);
</code></span></code></pre>
<blockquote class="content-quote">
<p>注意：tsptr 时绝对时间（可以先获取当前时间，再计算截止时间），不是相对时间。</p>
</blockquote>
<h4 id="1164">11.6.4 读写锁</h4>
<p>写锁状态：加读锁会阻塞，加写锁会阻塞</p>
<p>读锁状态：加读锁都会被允许，加写锁会阻塞</p>
<p>（一般加写锁阻塞后，加读锁的请求也会被阻塞，防止一直有程序占有读锁）</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;

int pthread_rwlock_init(      pthread_rwlock_t     *restrict rwlock,
                        const pthread_rwlockarrt_t *restrict arrt); 

int pthread_rwlock_destroy  (pthread_rwlock_t *rwlock);

int pthread_rwlock_rdlock   (pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock   (pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock   (pthread_rwlock_t *rwlock);

/* 尝试加读写锁 */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

/* 带超时的读写锁 */
int pthread_rwlock_timedrdlock(     pthread_rwlock_t *restrict rwlock,
                               const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(     pthread_rwlock_t *restrict rwlock,
                               const struct timespec *restrict tsptr);
</code></span></code></pre>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdlib.h&gt;
#include &lt;pthread.h&gt;

struct job {
    struct job *j_next;
    struct job *j_prev;
    pthread_t   j_id;   /* tells which thread handles this job */
    /* ... more stuff here ... */
};

struct queue {
    struct job      *q_head;
    struct job      *q_tail;
    pthread_rwlock_t q_lock;
};

/*
 * Initialize a queue.
 */
int queue_init(struct queue *qp)
{
    int err;

    qp-&gt;q_head = NULL;
    qp-&gt;q_tail = NULL;
    err = pthread_rwlock_init(&amp;qp-&gt;q_lock, NULL);
    if (err != 0) {
        return(err);
    }
    /* ... continue initialization ... */
    return(0);
}

/*
 * Insert a job at the head of the queue.
 */
void job_insert(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&amp;qp-&gt;q_lock);
    jp-&gt;j_next = qp-&gt;q_head;
    jp-&gt;j_prev = NULL;
    if (qp-&gt;q_head != NULL) {
        qp-&gt;q_head-&gt;j_prev = jp;
    }
    else {
        qp-&gt;q_tail = jp;    /* list was empty */
    }
    qp-&gt;q_head = jp;
    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);
}

/*
 * Append a job on the tail of the queue.
 */
void job_append(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&amp;qp-&gt;q_lock);
    jp-&gt;j_next = NULL;
    jp-&gt;j_prev = qp-&gt;q_tail;
    if (qp-&gt;q_tail != NULL) {
        qp-&gt;q_tail-&gt;j_next = jp;
    } else {
        qp-&gt;q_head = jp;    /* list was empty */
    }
    qp-&gt;q_tail = jp;
    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);
}

/*
 * Remove the given job from a queue.
 */
void job_remove(struct queue *qp, struct job *jp)
{
    pthread_rwlock_wrlock(&amp;qp-&gt;q_lock);
    if (jp == qp-&gt;q_head) {
        qp-&gt;q_head = jp-&gt;j_next;
        if (qp-&gt;q_tail == jp)
            qp-&gt;q_tail = NULL;
        else
            jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;
    } else if (jp == qp-&gt;q_tail) {
        qp-&gt;q_tail = jp-&gt;j_prev;
        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;
    } else {
        jp-&gt;j_prev-&gt;j_next = jp-&gt;j_next;
        jp-&gt;j_next-&gt;j_prev = jp-&gt;j_prev;
    }
    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);
}

/*
 * Find a job for the given thread ID.
 */
struct job *job_find(struct queue *qp, pthread_t id)
{
    struct job *jp;

    if (pthread_rwlock_rdlock(&amp;qp-&gt;q_lock) != 0)
        return(NULL);

    for (jp = qp-&gt;q_head; jp != NULL; jp = jp-&gt;j_next)
        if (pthread_equal(jp-&gt;j_id, id))
            break;

    pthread_rwlock_unlock(&amp;qp-&gt;q_lock);
    return(jp);
}
</code></span></code></pre>
<h4 id="1166">11.6.6 条件变量</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;

// 成功返回0，失败返回错误编号
int pthread_cond_init(      pthread_cond_t     *restrict cond,
                      const pthread_condarrt_t *restrict arrt);

int pthread_cond_destroy(pthread_cond_t *restrict cond);

/*
等待条件变量的特殊条件发生；
pthread_cond_wait() 必须与一个 pthread_mutex 配套使用。

该函数调用实际上依次做了3件事：
对当前 pthread_mutex 解锁、
把当前线程挂起到当前条件变量的线程队列、
被其它线程的信号唤醒后对当前 pthread_mutex 申请加锁。

如果线程收到一个信号被唤醒，将被配套的互斥锁重新锁住，
pthread_cond_wait() 函数将不返回直到线程获得配套的互斥锁。

需要注意的是，一个条件变量不应该与多个互斥锁配套使用。
*/
int pthread_cond_wait(pthread_cond_t  *restrict cond,
                      pthread_mutex_t *restrict mutex): 

#include &lt;time.h&gt;

int pthread_cond_timedwait(pthread_cond_t  *restrict cond,
                           pthread_mutex_t *restrict mutex
                     const struct timespec *restrict tsptr):

/*
发送一个信号给正在当前条件变量的线程队列中处于阻塞等待状态的线程，使其脱离阻塞状态，唤醒后继续执行。

如果没有线程处在阻塞等待状态，pthread_cond_signal 也会成功返回。

一般只给一个阻塞状态的线程发信号。
假如有多个线程正在阻塞等待当前条件变量，则根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。
如果各线程优先级相同，则根据等待时间的长短来确定哪个线程获得信号。

但 pthread_cond_signal 在多处理器上可能同时唤醒多个线程，当只能让一个被唤醒的线程处理某个任务时，其它被唤醒的线程就需要继续 wait。

POSIX 规范要求 pthread_cond_signal 至少唤醒一个 pthread_cond_wait 上的线程，有些实现为了简便，在单处理器上也会唤醒多个线程。
所以最好对 pthread_cond_wait() 使用 while 循环对条件变量是否满足做条件判断。
*/
int pthread_cond_signal(pthread_cond_t  *restrict cond);

/*
某些应用，如线程池，pthread_cond_broadcast 唤醒全部线程，
但我们通常只需要一部分线程去做执行任务，所以其它的线程需要继续 wait.
*/
int pthread_cond_broadcast(pthread_cond_t  *restrict cond); 
</code></span></code></pre>
<h4 id="1167">11.6.7 自旋锁</h4>
<p>自旋锁与互斥量类似，但不通过休眠使进程阻塞，而是在获取锁之前一直处于忙等（自旋）阻塞状态。</p>
<p><strong>自旋锁通常作为底层原语，实现其他类型的锁。</strong></p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;

/*
pshared 参数表示进程共享属性，表明自旋锁是如何获取的。
这个属性只在支持线程进程共享同步的平台上用得到。
PTHREAD_PROCESS_SHARED，自旋锁能够被可以访问底层内存的线程获取；
PTHREAD_PROCESS_PRIVATE，自旋锁只能被初始化该锁的进程内部的线程锁访问。
*/
// 成功返回0，失败返回错误编号
int pthread_spin_init   (pthread_spinlock_t *spinlock, int pshared); 
int pthread_spin_destroy(pthread_spinlock_t *spinlock);

// 成功返回0，失败返回错误编号，如果 spin 已经上锁，会导致自旋
int pthread_spin_lock   (pthread_spinlock_t *spinlock);
int pthread_spin_unlock (pthread_spinlock_t *spinlock);

/* 尝试获取自旋锁 */
// 成功返回0，失败返回错误编号，如果 spin 已经上锁，会失败，返回 EBUSY
// 注意：pthread_spin_trylock 不能自旋
int pthread_spin_trylock(pthread_spinlock_t *spinlock);
</code></span></code></pre>
<blockquote class="content-quote">
<p>注意：在不持有锁的情况下，不要在调用可能进入休眠状态的函数，这会导致其他在自旋状态的线程白白等待。</p>
</blockquote>
<h4 id="1168">11.6.8 屏障</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;pthread.h&gt;

// 成功返回0，失败返回错误编号
int pthread_barrier_init(     pthread_barrier_t     *restrict barrier,
                        const pthread_barrierattr_t *restrict attr,
                              unsigned int           count);

int pthread_barrier_destory(pthread_barrier_t *barrier);

/* 
成功：当在该 barrier 处执行 pthread_barrier_wait 的线程数量达到预先设定值后，
该线程会得到 PTHREAD_BARRIER_SERIAL_THREAD 返回值，其他线程得到0返回值。
失败：失败返回错误编号
*/
int pthread_barrier_wait(pthread_barrier_t *barrier);
</code></span></code></pre>
<p>usage：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/*
base -- 指向要排序的数组的第一个元素的指针。
nitems -- 由 base 指向的数组中元素的个数。
size -- 数组中每个元素的大小，以字节为单位。
compar -- 用来比较两个元素的函数。
*/
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
</code></span></code></pre>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">// #include &quot;apue.h&quot;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;pthread.h&gt;
#include &lt;limits.h&gt;
#include &lt;sys/time.h&gt;

#define NTHR   8                /* number of threads */
#define NUMNUM 8000000L         /* number of numbers to sort */
#define TNUM   (NUMNUM/NTHR)    /* number to sort per thread */

long nums[NUMNUM];
long snums[NUMNUM];

pthread_barrier_t b;

/*
 * Compare two long integers (helper function for heapsort)
 */
int complong(const void *arg1, const void *arg2)
{
    long l1 = *(long *)arg1;
    long l2 = *(long *)arg2;

    if (l1 == l2)       return 0;
    else if (l1 &lt; l2)   return -1;
    else                return 1;
}

/*
 * Worker thread to sort a portion of the set of numbers.
 */
void *thr_fn(void *arg)
{
    long    idx = (long)arg;

    qsort(&amp;nums[idx], TNUM, sizeof(long), complong);
    pthread_barrier_wait(&amp;b);

    /*
     * Go off and perform more work ...
     */
    return((void *)0);
}

/*
 * Merge the results of the individual sorted ranges.
 */
void merge(void)
{
    long    idx[NTHR];
    long    i, minidx, sidx, num;

    for (i = 0; i &lt; NTHR; i++) {
        idx[i] = i * TNUM;
    }
    for (sidx = 0; sidx &lt; NUMNUM; sidx++) {
        num = LONG_MAX;
        for (i = 0; i &lt; NTHR; i++) {
            if ((idx[i] &lt; (i+1)*TNUM) &amp;&amp; (nums[idx[i]] &lt; num)) {
                num = nums[idx[i]];
                minidx = i;
            }
        }
        snums[sidx] = nums[idx[minidx]];
        idx[minidx]++;
    }
}

int main(int argc, char *argv[])
{
    unsigned long   i;
    struct timeval  start, end;
    long long       startusec, endusec;
    double          elapsed;
    int             err;
    pthread_t       tid;

    /*
     * Create the initial set of numbers to sort.
     */
    printf(&quot;Create the initial set of numbers to sort.\n&quot;);
    srandom(1);
    for (i = 0; i &lt; NUMNUM; i++) {
        nums[i] = random();
    }

    /*
     * Create 8 threads to sort the numbers.
     */
    printf(&quot;Create 8 threads to sort the numbers.\n&quot;);
    gettimeofday(&amp;start, NULL);
    pthread_barrier_init(&amp;b, NULL, NTHR+1);
    for (i = 0; i &lt; NTHR; i++) {
        printf(&quot;pthread_create %ld.\n&quot;, i);
        err = pthread_create(&amp;tid, NULL, thr_fn, (void *)(i * TNUM));
        if (err != 0) {
            // err_exit(err, &quot;can't create thread&quot;);
            fprintf(stderr, &quot;Can't creat thread: %s&quot;, strerror(err));
            exit(1);
        }
    }
    pthread_barrier_wait(&amp;b);
    merge();
    gettimeofday(&amp;end, NULL);

    /*
     * Print the sorted list.
     */
    startusec = start.tv_sec * 1000000 + start.tv_usec;
    endusec = end.tv_sec * 1000000 + end.tv_usec;
    elapsed = (double)(endusec - startusec) / 1000000.0;
    printf(&quot;sort took %.4f seconds\n&quot;, elapsed);
    for (i = 0; i &lt; 10; i++) {
        printf(&quot;%ld\n&quot;, snums[i]);
    }
    printf(&quot;...\n&quot;);
    for (i = NUMNUM-10; i &lt; NUMNUM; i++) {
        printf(&quot;%ld\n&quot;, snums[i]);
    }
    exit(0);
}

</code></span></code></pre>
<h2 id="_11">第十二章 线程控制</h2>
<h3 id="123">12.3 线程属性</h3>
<h3 id="124">12.4 同步属性</h3>
<h4 id="1241">12.4.1 互斥量属性</h4>
<h4 id="1242">12.4.2 读写锁属性</h4>
<h4 id="1243">12.4.3 条件变量属性</h4>
<h4 id="1244">12.4.4 屏障属性</h4>
<h3 id="125">12.5 重入</h3>
<p>如果一个函数在相同的时间点内可以被多个 <strong>线程</strong> 安全地调用，则称该函数是 <strong>线程安全</strong> 的。</p>
<p><u>线程安全的函数，不能说明对信号处理程序来说该函数时可重入的。</u></p>
<p>如果一个函数在相同的时间点内可以被多个 <strong>异步信号处理程序</strong> 安全地调用，则称该函数是 <strong>异步信号安全</strong> 的。</p>
<blockquote class="content-quote">
<p>（POSIX.1不能保证线程安全的函数，和替代的线程安全函数，见书，带添加）</p>
</blockquote>
<p>操作 FILE 对象的标准 I/O 例程的动作行为看起来必须就像他们内部调用了 flockfile，funlockfile</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;

// 成功返回0，失败（不能获取锁），返回非0
int  ftrylockfile(FILE *fp);

void flockfile(FILE *fp);
void funlockfile(FILE *fp);

// 这个锁是递归的，当占有这把锁的时候，还是可以再次获取该锁，而且不会导致死锁。
</code></span></code></pre>
<p>不加锁的标准 I/O 例程：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;

int getchar_unlocked(void);
int getc_unlocked(FILE *fp);

int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
</code></span></code></pre>
<h2 id="io_2">第十四章 高级 I/O</h2>
<h3 id="142-io">14.2 非阻塞 I/O</h3>
<h3 id="143">14.3 记录锁</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;fcntl.h&gt;

/* 
F_GETLK 根据lock参数值，决定是否上文件锁
F_SETLK 设置lock参数值的文件锁
F_SETLKW 这是 F_GETLK的阻塞版本，在无法获取锁时，会进入睡眠状态。
*/

int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *flockptr);

struct flock
{
    short l_type;
    /*  F_RDLCK 共享读锁
        F_WRLCK 独占性写锁
        F_UNLCK 删除之前建立的锁定 */
    short l_whence;
    /*  SEEK_SET 以文件开头为锁定的起始位置
        SEEK_CUR 以目前文件读写位置为锁定的起始位置
        SEEK_END 以文件结尾为锁定的起始位置 */
    off_t l_start;
    /*  表示相对 l_whence 位置的偏移量，两者一起确定锁定区域的开始位置 */
    off_t l_len;
    /*  表示锁定区域的长度
        如果为0表示从起点(由 l_whence 和 l_start 决定的开始位置)开始
        直到最大可能偏移量为止。即不管在后面增加多少数据都在锁的范围内。*/
    pid_t l_pid;
    /*  进程的 ID 持有的所能阻塞当前进程，仅由 F_GETLK 返回 */
};
</code></span></code></pre>
<h4 id="1431">14.3.1 锁的隐含继承和释放</h4>
<ul>
<li>锁与进程和文件两者相关联。</li>
<li>当一个进程终止时，它建立的所有锁全部释放。</li>
<li>无论一个描述符如何关闭，该进程通过这一描述符引用的文件上的任何一把锁都会释放。（当两个文件描述符指向同一个文件时，通过其中一个文件描述符关闭文件，文件上的锁会被释放）</li>
<li>由 fork 产生的子进程不会继承父进程锁设置的锁。</li>
<li>执行 exec 后，新程序可以继承原执行程序的锁。</li>
</ul>
<h4 id="1432">14.3.2 在文件末端加锁</h4>
<h4 id="1433">14.3.3 建议性锁和强制性锁</h4>
<p>强制性锁会让内核检查每一个 open，read，write，验证调用进程是否违背了正在访问的文件上的一把锁。</p>
<h3 id="144-io">14.4 I/O多路转接</h3>
<h4 id="1441-select-pselect">14.4.1 select 和 pselect</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/select.h&gt;

struct timeval
{
    time_t tv_sec;
    time_t tv_usec;
};

/** @brief 用来等待文件描述词状态的改变。
 *  @param maxfdp1      代表最大的文件描描述符【加 1】（即描述符数量）
 *  @param readfds      可读文件描述符集合，可以为 NULL
 *  @param writefds     可写文件描述符集合，可以为 NULL
 *  @param exceptfds    处于异常的描述符集合，可以为 NULL
 *  @param tvptr == NULL，永远等待，直到捕捉到一个信号。
 *         tvptr-&gt;tv_sec == 0 &amp;&amp; tvptr-&gt;tv_usec == 0
 *         根本不等待，测试所有指定的文件描述符。
 *         tvptr-&gt;tv_sec != 0 || tvptr-&gt;tv_usec != 0
 *         等待一定时间。
 *  @return 准备就绪的文件描述符数目，超时返回0，失败返回-1
 */
int select(int maxfdp1, 
           fd_set *restrict readfds,
           fd_set *restrict writefds,
           fd_set *restrict exceptfds,
           struct timeval *restrict tvptr);

int pselect(int maxfdp1, 
           fd_set *restrict readfds,
           fd_set *restrict writefds,
           fd_set *restrict exceptfds,
           const struct timespec *restrict tsptr,
           const sigset_t *restrict sigmask);

// FD_ISSET：fd 在文件描述符集合中返回非0（true），返回0（false）
int  FD_ISSET(int fd, fd_set *fdset); 
void FD_CLR  (int fd, fd_set *fdset);
void FD_SET  (int fd, fd_set *fdset);
void FD_ZERO         (fd_set *fdset);
</code></span></code></pre>
<h4 id="1442-poll">14.4.2 poll</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;poll.h&gt;

struct pollfd {
    int fd;         /* 文件描述符 */
    short events;   /* 等待的需要测试事件 */
    short revents;  /* 实际发生了的事件，也就是返回结果 */
};

typedef unsigned long nfds_t

/** @brief 用来等待文件描述词状态的改变。
 *  @param fds          用于存放需要检测其状态的 Socket 描述符；
 *  @param nfds         标记数组 fds 中的结构体元素的总数量；
 *  @param timeout      poll 函数调用阻塞的时间，单位：毫秒；
 *                      timeout == -1 永远等待
 *                      timeout == 0 不等待
 *                      timeout &gt; 0 等待 ms
 *  @return 准备就绪的文件描述符数目，超时返回0，失败返回-1
 */
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
</code></span></code></pre>
<h5 id="poll">poll 函数可用的测试值</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th><strong>常量</strong></th>
<th align="center">events</th>
<th align="center">revents</th>
<th><strong>说明</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>POLLIN</td>
<td align="center">-</td>
<td align="center">-</td>
<td>普通或优先级带数据可读</td>
</tr>
<tr>
<td>POLLRDNORM</td>
<td align="center">-</td>
<td align="center">-</td>
<td>普通数据可读</td>
</tr>
<tr>
<td>POLLRDBAND</td>
<td align="center">-</td>
<td align="center">-</td>
<td>优先级带数据可读</td>
</tr>
<tr>
<td>POLLPRI</td>
<td align="center">-</td>
<td align="center">-</td>
<td>高优先级数据可读</td>
</tr>
<tr>
<td>POLLOUT</td>
<td align="center">-</td>
<td align="center">-</td>
<td>普通数据可写</td>
</tr>
<tr>
<td>POLLWRNORM</td>
<td align="center">-</td>
<td align="center">-</td>
<td>普通数据可写</td>
</tr>
<tr>
<td>POLLWRBAND</td>
<td align="center">-</td>
<td align="center">-</td>
<td>优先级带数据可写</td>
</tr>
<tr>
<td>POLLERR</td>
<td align="center"></td>
<td align="center">-</td>
<td>发生错误</td>
</tr>
<tr>
<td>POLLHUP</td>
<td align="center"></td>
<td align="center">-</td>
<td>发生挂起</td>
</tr>
<tr>
<td>POLLNVAL</td>
<td align="center"></td>
<td align="center">-</td>
<td>描述字不是一个打开的文件</td>
</tr>
</tbody>
</table></div>
<blockquote class="content-quote">
<ul>
<li>每当调用这个函数之后，系统不会清空这个数组，操作起来比较方便；特别是对于 socket 连接比较多的情况下，在一定程度上可以提高处理的效率；</li>
<li>这一点与 select() 函数不同，调用 select() 函数之后，select() 函数会清空它所检测的 socket 描述符集合，导致每次调用 select() 之前都必须把 socket 描述符重新加入到待检测的集合中；</li>
<li>因此，select() 函数适合于只检测一个 socket 描述符的情况，而 poll() 函数适合于大量socket描述符的情况；</li>
</ul>
</blockquote>
<h2 id="interprocess-communicationipc">第十五章 进程通信（InterProcess Communication，IPC）</h2>
<h3 id="152">15.2 管道</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;unistd.h&gt;
/*
f[0] 为读而打开
f[1] 为写而打开
f[1] 的输出是 f[0] 的输入
*/
// 成功返回0，出错返回-1
int pipe(int fd[2]);
</code></span></code></pre>
<p>当管道的一端被关闭之后：</p>
<ul>
<li>当 <strong>读</strong> 一个 <strong>写端已被关闭</strong> 的管道，在所有数据已经被读取之后，返回0，表示文件结束。</li>
<li>当 写 一个读端已被关闭的管道，则产生信号 SIGPIPE，write返回-1，errno设置为EIPE</li>
</ul>
<h3 id="153-popen-pclose">15.3 popen 和 pclose</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;stdio.h&gt;
FILE *popen(const char *cmdsring, const char *type);
int pclose(FILE *fp);
</code></span></code></pre>
<p>popen，先执行 fork，然后调用 exce 执行 cmdstring，并且返回一个标准 I/O文件指针。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">fp = popen(cmdstring, &quot;r&quot;);
父进程 fp &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt; stdout 子进程（cmdstring）
fp = popen(cmdstring, &quot;w&quot;);
父进程 fp &gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; stdin  子进程（cmdstring）
</code></span></code></pre>
<h3 id="154">15.4 协同进程</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">父进程              子进程（协同进程）
fd[1] &gt;&gt;&gt;&gt; pip1 &gt;&gt;&gt;&gt; stdin  
fd[0] &lt;&lt;&lt;&lt; pip2 &lt;&lt;&lt;&lt; stdout
</code></span></code></pre>
<h3 id="155-fifo">15.5 FIFO</h3>
<ul>
<li>FIFO 命名管道。</li>
<li>未命名的管道如（pipe）只能在两个相关的进程之间使用，而且这两个相关的进程还要有一个共同的祖先进程。</li>
</ul>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sts/stat.h&gt;

// 成功返回0，出错返回-1
int mkfifo          (const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
/*
mkfifoat：
path 绝对路径名，fd 参数会被忽略掉，mkfifoat 的行为和 mkfifo 类似。
path 相对路径名，则 fd 是一个打开目录的有效文件描述符。
path 相对路径名，fd 是 AT_FDCWD，则路径名从当前路径开始。
*/
</code></span></code></pre>
<h3 id="157">15.7 消息队列</h3>
<h3 id="158">15.8 信号量</h3>
<h3 id="159">15.9 共享存储</h3>
<p>共享存储允许两个或多个进程共享一个给定的存储区，因为市局不需要在客户进程和服务器进程之间复制，这是最快的一种 IPC。</p>
<p>共享存储的同步可以使用信号量、记录锁、互斥量完成。</p>
<blockquote class="content-quote">
<p>在多个进程之间共享一个资源，可以使用3中技术来协调访问：</p>
<ul>
<li>信号量：创建一个包含一个成员的信号量集合个，并初始化为1，对每个 semop 操作都设置 SEM_UNDO，可以在进程终止时，自动处理未释放的信号量。</li>
<li>记录锁：创建一个空文件，并且用该文件的第一个字节作为锁字节。<strong>记录锁的性质确保当一个锁的持有者进程终止时，内核会自动释放该锁。</strong></li>
<li>互斥量：所有进程将相同的文件映射到他们的地址空间，并用 PTHREAD<em>PROCESS</em>SHARED 互斥属性在文件的相同偏移处初始化互斥量。如果一个进程没有释放互斥量而终止，恢复非常困难，除非使用 pthread<em>mutex</em>consistent 函数。</li>
</ul>
</blockquote>
<h2 id="_12">第十六章 套接字</h2>
<h3 id="162">16.2 套接字描述符</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;

/** @brief 用来建立一个新的socket，也就是向系统注册，通知系统建立一通信端口。
 *  @param domain   指定使用何种的地址类型，
 *                  完整的定义在/usr/include/bits/socket.h
 *                  AF_INET         Ipv4 网络协议
 *                  AF_INET6        Ipv6 网络协议
 *                  AF_UNIX         UNIX 进程通信协议
 *                  AF_UNSPEC       未指定
 *                  AF_IPX          IPX-Novell 协议
 *                  ...
 *
 *  @param type     套接字类型，有下列几种数值:
 *                  SOCK_DGRAM      固定长度的、无连接、不可靠的报文传递
 *                  SOCK_RAW IP     协议的数据报接口
 *                  SOCK_SEQPACKET  固定长度的、有序、可靠的、面向连接的报文传递
 *                  SOCK_STREAM     提供有序的，可靠的，双向的，面向连接的字节流，即TCP。
 *                  支持OOB 机制，在所有数据传送前必须使用connect()来建立连线状态。
 *                  
 *  @param protocol 用来指定 socket 所使用的传输协议编号，通常此参考不用管它，设为0即可。
 *                  IPPROTO_IP      IPv4 网际协议
 *                  IPPROTO_IPV6    IPv6 网际协议
 *                  IPPROTO_ICMP    因特网控制报文协议
 *                  IPPROTO_RAW     原始 IP 数据包协议
 *                  IPPROTO_TCP     传输控制协议
 *                  IPPROTO_UDP     用户数据报协议
 *  @return 成功，返回文件描述符，出错返回 -1
 */
int socket(int domain, int type, int protocol);

/** @brief 终止 socket 链接
 *  @param sockfd   套接字文件描述符
 *  @param how      how = SHUT_RD  (0) 终止读取操作
 *                  how = SHUT_WR  (1) 终止传送操作
 *                  how = SHUT_RDWR(2) 终止读取及传送操作
 *  @return 成功，返回0，出错返回 -1，错误原因存于errno。
 *          错误代码 
 *          EBADF       参数 s 不是有效的 socket 处理代码
 *          ENOTSOCK    参数 s 为一文件描述词，非 socket
 *          ENOTCONN    参数 s 指定的 socket 并未连线
 */
int shutdown(int sockfd, int how);

int close(int fd); 
// 也可以用于关闭 socket，但是要等到所有引用 socket 的文件描述符都 close 之后，才会真正释放这个套接字，而 shutdown 可以直接断开 socket 的连接，而不管由多少个文件描述符指向这个 socket
</code></span></code></pre>
<h3 id="163">16.3 寻址</h3>
<h4 id="1631">16.3.1 字节序</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;arpa/inet.h&gt;
uint32_t htonl(unit32_t hostint32);     // 返回值：以网络字节序表示的32位整数
uint16_t htons(unit16_t hostint16);     // 返回值：以网络字节序表示的16位整数
uint32_t ntohl(unit32_t netint32);      // 返回值：以主机字节序表示的32位整数
uint32_t ntons(unit16_t netint16);      // 返回值：以主机字节序表示的16位整数
</code></span></code></pre>
<h4 id="1632">16.3.2 地址格式</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">struct sockaddr {
    sa_family_t     sa_family;
    char            ds_data[14];
};

#include &lt;netinet/in.h&gt;

/* IPv4 Internet address.  */
/* IPv4 Internet address.  */
/* IPv4 Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr {
    in_addr_t       s_addr;
};

/* Structure describing an Internet socket address.  */
struct sockaddr_in {
    sa_family_t     sin_family;     /* Address family.  */
    in_port_t       sin_port;       /* Port number.  */
    struct in_addr  sin_addr;       /* Internet address.  */
};

/* IPv6 Internet address.  */
/* IPv6 Internet address.  */
/* IPv6 Internet address.  */
struct in6_addr{
    uint8_t         s6_addr[16];
};

struct sockaddr_in6{
    sa_family_t     sin6_family;
    in_port_t       sin6_port;
    uint32_t        sin6_flowinfo;
    struct in6_addr sin6_addr;
    uint32_t        sin6_scope_id;
};
</code></span></code></pre>
<p>二进制地址格式转换为点分十进制字符表示</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/** @brief 将网络字节序的二进制地址转化为文本字符串格式
 *  @param domain   指定使用何种的地址类型，只支持下面两种
 *                  AF_INET         Ipv4 网络协议
 *                  AF_INET6        Ipv6 网络协议
 *  @return 成功，返回字符串指针
 *          错误，返回 NULL
 */
const char *inet_ntop(int                   domain, 
                      const void *restrict  addr,
                      char       *restrict  str, 
                      socklen_t             size);

/** @brief 将文本字符串格式转换为网络字节序的二进制地址
 *  @param domain   指定使用何种的地址类型，只支持下面两种
 *                  AF_INET         Ipv4 网络协议
 *                  AF_INET6        Ipv6 网络协议
 *  @return 成功，返回1
 *          格式无效，返回0
 *          出错，返回-1
 */
int inet_pton(int                   domain, 
              const char *restrict  str,
              void *restrict        addr);
</code></span></code></pre>
<h4 id="1633">16.3.3 地址查询</h4>
<h5 id="hostent">hostent</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;netdb.h&gt;

/* Description of data base entry for a single host.  */
struct hostent
{
    char       *h_name;         /* Official name of host.  */
    char      **h_aliases;      /* Alias list.  */
    int         h_addrtype;     /* Host address type.  */
    int         h_length;       /* Length of address.  */
    char      **h_addr_list;    /* List of addresses from name server.  */
    ...
};

// 找到给定计算机系统的主机信息
struct hostent *gethostent(void);
void sethostent(int stayopen);
void endhostent(void);
</code></span></code></pre>
<h5 id="protoent">protoent</h5>
<p>我们可以用以下函数在协议名字和协议编号之间进行映射：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;netdb.h&gt;

// POSIX.1 定义的 protoent 结构至少包含以下成员： 
struct protoent {
    char  *p_name;       /* official protocol name */
    char **p_aliases;    /* alias list */
    int    p_proto;      /* protocol number */
};

struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
void setprotoent(int stayopen);
void endprotoent(void);
</code></span></code></pre>
<h5 id="servent">servent</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;netdb.h&gt;

// POSIX.1 定义的 servent 结构至少包含以下成员： 
struct servent {
    char  *s_name;       /* official service name */
    char **s_aliases;    /* alias list */
    int    s_port;       /* port number */
    char  *s_proto;      /* protocol to use */
};

struct servent *getservbyname(const char *name, const char *proto); // 将一个服务名映射到一个端口号
struct servent *getservbyport(int port, const char *proto); // 将一个端口号映射到一个服务名
struct servent *getservent(void); // 顺序扫描服务数据库
void setservent(int stayopen);
void endservent(void);
</code></span></code></pre>
<h5 id="netent">netent</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;netdb.h&gt;

struct netent {
    char       *n_name;         /* Official name of host.  */
    char      **n_aliases;      /* Alias list.  */
    int         n_addrtype;     /* Host address type.  */
    uint32_t    n_net;          /* Length of address.  */
    ...
};

// struct netent *getnetbyaddr(uint32_t net, int type);
// struct netnet *getnetbyname(const char *name);
struct netnet *getnetent(void);
void setnetent(int stayopen);
void endnetent(void);
</code></span></code></pre>
<h5 id="addrinfo">addrinfo</h5>
<p>POSIX.1 定义了若干新的函数，允许一个应用程序将一个主机名和一个服务器名映射到一个地址</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;
#include &lt;netdb.h&gt;

struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

// 主机名 服务器名 -&gt; 地址
int getaddrinfo(const char             *restrict host,
                const char             *restrict service,
                const struct addrinfo  *restrict hint,
                      struct addrinfo **restrict res);

void freeaddrinfo(struct addrinfo *ai);

// 如果 getaddrinfo 失败，需要调用 gai_strerror 将返回的错误码转换成错误消息
const char *gai_strerror(int error);

// 地址 -&gt; 主机名 服务器名
int getnameinfo(const struct sockaddr *restrict addr,
                      socklen_t                 alen,
                      char            *restrict host,
                      socklen_t                 hostlen,
                      char            *restrict service,
                      socklen_t                 servlen,
                      int                       flags);
</code></span></code></pre>
<h3 id="1634">16.3.4 将套接字与地址相关联</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;std/socket.h&gt;

/** @brief 将一个套接字绑定到一个地址上
 *  @param addr     struct sockaddr_in 或 struct sockaddr_in6 的对象
 *  @param len      *addr 对象的字节数
 *  @return 成功，返回0；出错，返回-1
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
</code></span></code></pre>
<p>对于地址的一些限制：</p>
<ul>
<li>在进程正在运行的计算机上，指定的地址必须有效；</li>
<li>地址必须和创建套接字时的地址所支持的格式相匹配；</li>
<li>地址中的端口号必须大于 1024，除非该应用进程具有相应的特权（超级用户）；</li>
<li>一般只能将一个套接字绑定到一个给定的地址上。</li>
</ul>
<h5 id="1_2">1）得到套接字相关地址</h5>
<p>可以调用 getsockname 函数来发现绑定到套接字上的地址。</p>
<p>如果套接字已经和对等方连接，可以调用 getpeername 函数找到对方的地址 。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
</code></span></code></pre>
<h3 id="164">16.4 建立连接</h3>
<p>如果处理一个面向连接的网络服务，那么开始交换数据之前，需要使用 connect 函数来建立连接。</p>
<h5 id="connect">connect</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;

/** @brief 将参数 sockfd 的 socket 连至参数 serv_addr 指定的网络地址。
 *  @param sockaddr     请参考 bind()
 *  @param addrlen      sockaddr 的结构长度
 *  @return 成功，返回0；出错，返回-1，错误原因存于errno中。
 *          错误代码 
 *          EBADF           参数 sockfd 非合法 socket 处理代码
 *          EFAULT          参数 serv_addr 指针指向无法存取的内存空间
 *          ENOTSOCK        参数 sockfd 为一文件描述词，非 socket。
 *          EISCONN         参数 sockfd 的 socket 已是连线状态
 *          ECONNREFUSED    连线要求被 server 端拒绝。
 *          ETIMEDOUT       企图连线的操作超过限定时间仍未有响应。
 *          ENETUNREACH     无法传送数据包至指定的主机。
 *          EAFNOSUPPORT    sockaddr 结构的 sa_family 不正确。
 *          EALREADY        socket 为不可阻断且先前的连线操作还未完成。
 */
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
</code></span></code></pre>
<h5 id="listen">listen</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;

/** @brief 用来等待参数 sockfd 的 socket 连线
 *  @param sockfd       
 *  @param backlog      指定同时能处理的最大连接要求，
 *                      如果连接数目达此上限，client 端将收到 ECONNREFUSED 的错误
 *  @return 成功则返回 0，失败返回 -1，错误原因存于 errno
 */
int listen(int sockfd, int backlog);
</code></span></code></pre>
<blockquote class="content-quote">
<p>Listen() 并未开始接收连线，只是设置 socket 为 listen 模式，真正接收 client 端连线的是accept()。</p>
<p>通常 listen() 会在 socket()，bind() 之后调用，接着才调用 accept()。</p>
</blockquote>
<h5 id="accept">accept</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include &lt;sys/socket.h&gt;

/** @brief 用来接受参数 sockfd 的 socket 连线。
 *  @param sockfd       sockfd 的 socket 必需先经bind()、listen()函数处理过，
 *                      当有连线进来时 accept() 会返回一个新的 socket 描述符，
 *                      往后的数据传送与读取就是经由新的 socket 处理，
 *                      而原来的 socket 能继续使用 accept() 来接受新的连线要求。
 *  @param addr         所指的结构会被系统填入远程主机的地址数据.
 *  @param addrlen      为 scokaddr 的结构长度。
 *  @return 成功则返回新的 socket 处理代码，失败返回-1，错误原因存于 errno 中。
 *             EBADF        参数 sockfd 非合法 socket 处理代码。
 *             EFAULT       参数 addr 指针指向无法存取的内存空间。
 *             ENOTSOCK     参数 sockfd 为一文件描述符，非 socket。
 *             EOPNOTSUPP   指定的 socket 并非SOCK_STREAM。
 *             EPERM        防火墙拒绝此连线。
 *             ENOBUFS      系统的缓冲内存不足。
 *             ENOMEM       核心内存不足。
 */
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
</code></span></code></pre>
<h5 id="send">send</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#include&lt;sys/socket.h&gt;

struct msghdr
{
    void          *msg_name;    /*Address to send to /receive from . */
    socklen_t     msg_namelen;  /* Length of addres data */
    strcut iovec  *msg_iov;     /* Vector of data to send/receive into */
    size_t        msg_iovlen;   /* Number of elements in the vector */
    void          *msg_control; /* Ancillary dat */
    size_t        msg_controllen; /* Ancillary data buffer length */
    int           msg_flags;    /* Flags on received message */
};

int send   (int sockfd, const void *msg, int len, unsigned int falgs);
int sendmsg(int sockfd, const strcut msghdr *msg, unsigned int flags);
int sendto (int sockfd, const void *msg, int len, unsigned int flags, const 
struct sockaddr *to, int tolen) ;
</code></span></code></pre>
<blockquote class="content-quote">
<p>记一次 Socket 编程问题：</p>
<p>客户端：</p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>shell
  Succeedfully connected to server 127.0.0.1:8080
  Enter: ^C # 服务器端终止
  user 15:45:28 ~ $</code></span></p>
<p>服务器端：</p>
<p>```shell</p>
<hr />
<p>Waiting for the connection&hellip;<br />
  Accept successfully!<br />
  - IP = 127.0.0.1<br />
  - Port = 58820<br />
  Accept successfully!<br />
  - IP = 127.0.0.1<br />
  - Port = 58822<br />
  recv ???<br />
  recv ??? # 直接终止<br />
  user 15:45:28 ~ $<br />
  ```</p>
<p>现象：客户端进程终止导致服务器端进程也终止。</p>
<p>原因：内核给出了 SIGPIPE 信号（写至无读端的进程的管道，默认动作终止）</p>
<p>信号产生的原因：</p>
<p>简单来说，就是客户端程序向服务器端程序发送了消息，然后关闭客户端，服务器端返回消息的时候就会收到内核给的 SIGPIPE 信号。</p>
<p>TCP 的全双工信道其实是两条单工信道，client 端调用 close 的时候，虽然本意是关闭两条信道，但是其实只能关闭它发送的那一条单工信道，还是可以接受数据，server 端还是可以发送数据，并不知道 client 端已经完全关闭了。</p>
<p>以下为引用：“对一个已经收到 FIN 包的 socket 调用 read 方法，如果接收缓冲已空，则返回0, 这就是常说的表示连接关闭。但第一次对其调用 write 方法时，如果发送缓冲没问题，会返回正确写入(发送)。但发送的报文会导致对端发送 RST 报文，因为对端的 socket 已经调用了close，完全关闭，既不发送，也不接收数据。所以，第二次调用 write 方法(假设在收到 RST 之后)，会生成 SIGPIPE 信号，导致进程退出.”’ <br />
  ————————————————<br />
  版权声明：本文为CSDN博主「哀酱」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。<br />
  原文链接：https://blog.csdn.net/u010821666/article/details/81841755</p>
</blockquote>
<h5 id="recv">recv</h5>
<h3 id="socket">socket 端口复用</h3>
<h4 id="_13">先关闭客户端再关闭服务器</h4>
<h5 id="1_3">1. 正常连接</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="3" align="center">Server : 8080</td>
        <td colspan="2" align="center">LISTEN</td>
    </tr>
    <tr>
        <td align="center">ESTABLISHED</td>
        <td rowspan="2" align="center">Client : 60954</td>
    </tr>
    <tr>
        <td align="center">ESTABLISHED</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ netstat -an|grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:8080          127.0.0.1:60954         ESTABLISHED
tcp        0      0 127.0.0.1:60954         127.0.0.1:8080          ESTABLISHED
</code></span></code></pre>
<h5 id="2-clinet">2. 先关闭客户端 Clinet</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="3" align="center">Server : 8080</td>
        <td colspan="2" align="center">LISTEN</td>
    </tr>
    <tr>
        <td align="center">close</td>
        <td rowspan="2" align="center">Client : 60954</td>
    </tr>
    <tr>
        <td align="center">TIME_WAIT</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ netstat -an|grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:60954         127.0.0.1:8080          TIME_WAIT
</code></span></code></pre>
<h5 id="3-server">3. 再关闭服务器 Server</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="2" align="center">Server : 8080</td>
        <td align="center">close</td>
        <td rowspan="2" align="center">Client : 60954</td>
    </tr>
    <tr>
        <td align="center">TIME_WAIT</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>$ netstat -an|grep 8080
tcp        0      0 127.0.0.1:60954         127.0.0.1:8080          TIME_WAIT
</code></span></code></pre>
<h4 id="_14">先关闭服务器再关闭客户端</h4>
<blockquote class="content-quote">
<p>导致服务器不能立即重</p>
</blockquote>
<h5 id="1_4">1. 正常连接</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="3" align="center">Server : 8080</td>
        <td colspan="2" align="center">LISTEN</td>
    </tr>
    <tr>
        <td align="center">ESTABLISHED</td>
        <td rowspan="2" align="center">Client : 60962</td>
    </tr>
    <tr>
        <td align="center">ESTABLISHED</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ netstat -an|grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:8080          127.0.0.1:60962         ESTABLISHED
tcp        0      0 127.0.0.1:60962         127.0.0.1:8080          ESTABLISHED
</code></span></code></pre>
<h5 id="2-server">2. 先关闭服务器 Server</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="2" align="center">Server : 8080</td>
        <td align="center">FIN_WAIT2</td>
        <td rowspan="2" align="center">Client : 60962</td>
    </tr>
    <tr>
        <td align="center">CLOSE_WAIT</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ netstat -an|grep 8080
tcp        0      0 127.0.0.1:8080          127.0.0.1:60962         FIN_WAIT2
tcp        1      0 127.0.0.1:60962         127.0.0.1:8080          CLOSE_WAIT
</code></span></code></pre>
<h5 id="3-client">3. 再关闭客户端 Client</h5>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal" border="1" cellspacing="0" style="table-layout:fixed;">
    <tr>
        <td rowspan="2" align="center">Server : 8080</td>
        <td align="center">TIME_WAIT</td>
        <td rowspan="2" align="center">Client : 60962</td>
    </tr>
    <tr>
        <td align="center">close</td>
    </tr>
</table></div>

<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-shell">$ netstat -an|grep 8080
tcp        0      0 127.0.0.1:8080          127.0.0.1:60962         TIME_WAIT
</code></span></code></pre>
<h5 id="_15">可以使用端口复用技术解决</h5>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">// 端口复用
int opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (const void*)&amp;opt, sizeof(opt));

// 绑定文件描述符和服务器的ip和端口号
bind(server_socket, (struct sockaddr *restrict)&amp;server_addr, sizeof(server_addr));
</code></span></code></pre>
  </div>
</div>
 
    </div>
  </div>
  <div id="footer-background">
    <div id="footer">
      <div class="legal pure-g">
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-license"><a href="https://beian.miit.gov.cn/#/Integrated/index">浙ICP备2020038748号</a></p>
  </div>
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-links"><a href="https://github.com/zromyk">GitHub</a></p>
    <p class="legal-copyright">Copyright © 2021 Wei Zhou. 保留所有权利。</p>
  </div>
</div>
    </div>
  </div>
  <!-- <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> -->
  <script src="/style/html/jquery.min.js"></script>
  <script src='/style/article/latex/latest.js?config=TeX-MML-AM_CHTML'></script>
<!-- <script src="https://cdn.geogebra.org/apps/deployggb.js"></script> -->
<script src="/style/article/deployggb.js"></script>
<!-- <script src="https://apps.bdimg.com/libs/highlight.js/9.1.0/highlight.min.js"></script> -->
<script type="text/javascript">
  // 脚本：navigation 随鼠标移动自动变换宽度
  var element = document.getElementById("navigation"); // 获取要操作的元素
  var elementWidth = parseInt(getComputedStyle(element).width);
  var elementLeft = 0;
  var elementRight = 0;
  element.addEventListener('mouseenter', function (event) { // 添加鼠标按下事件的监听器
    elementLeft = element.getBoundingClientRect().left - 10;
    elementRight = element.getBoundingClientRect().left + elementWidth * 3;
    window.addEventListener('mousemove', resize); // 添加全局的鼠标移动事件的监听器
  });

  function resize(event) {
    var minWidth = elementWidth;
    var maxWidth = elementWidth * 2.5;
    // console.log(elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    if (elementLeft <= event.clientX && event.clientX <= elementRight) {
      var width = event.clientX - elementLeft + elementWidth / 2;
      width = Math.min(width, maxWidth);
      width = Math.max(width, minWidth);
      element.style.width = width + 'px'; // 设置新的宽度样式属性
    }
    else {
      element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
      stopResize();
    }
  }

  function stopResize() {
    element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
    // console.log("stopResize", elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    window.removeEventListener('mousemove', resize); // 移除鼠标移动事件的监听器
  }
</script>
<script src="/style/article/highlight/highlight.min.js"></script>
<script type="text/javascript">
  // 脚本：code语法高亮
  hljs.initHighlightingOnLoad();
</script>
<script>
  function animateByNav() {
    $("html").animate({
        scrollTop: ($(event.target.hash).offset().top - 52)
    }, 300);
  };
</script>
<script src="/style/article/pell-1.0.6/dist/pell.js"></script>
<script>
  // 脚本：自由编辑页面
  var editor = window.pell.init({
    element: document.getElementById('editor'),
    defaultParagraphSeparator: 'p',
    onChange: function(html) {
        document.getElementById('text-output').innerHTML = html
        document.getElementById('html-output').textContent = html
    }
  });

  function markdownEditor() {
    var articles = document.getElementById('content-articles-markdown');
    if (articles.getAttribute("contenteditable") == "true") {
        articles.setAttribute("contenteditable", "false");
        document.getElementById("content-articles-markdownEditor").style.display = "none"; //隐藏
        document.getElementById("button-markdownEditor").innerHTML = "启用编辑";
    } else {
        articles.setAttribute("contenteditable", "true");
        document.getElementById("content-articles-markdownEditor").style.display = ""; //显示
        document.getElementById("button-markdownEditor").innerHTML = "关闭编辑";
    }
  };

  function save() {
      window.alert("保存成功");
  };
</script>

</body>
</html>
